blob: 39e1c6e291739f3a6c5177bf3749f488bc36b58b [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.example.asyncrest;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.AsyncContext;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Utf8StringBuilder;
import org.eclipse.jetty.util.ajax.JSON;
/**
* Servlet implementation class AsyncRESTServlet.
* Enquires ebay REST service for auctions by key word.
* May be configured with init parameters: <dl>
* <dt>appid</dt><dd>The eBay application ID to use</dd>
* </dl>
* Each request examines the following request parameters:<dl>
* <dt>items</dt><dd>The keyword to search for</dd>
* </dl>
*/
public class AsyncRestServlet extends AbstractRestServlet
{
final static String RESULTS_ATTR = "org.eclipse.jetty.demo.client";
final static String DURATION_ATTR = "org.eclipse.jetty.demo.duration";
final static String START_ATTR = "org.eclispe.jetty.demo.start";
HttpClient _client;
@Override
public void init(ServletConfig servletConfig) throws ServletException
{
super.init(servletConfig);
_client = new HttpClient();
try
{
_client.start();
}
catch (Exception e)
{
throw new ServletException(e);
}
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
Long start=System.nanoTime();
// Do we have results yet?
Queue<Map<String, String>> results = (Queue<Map<String, String>>) request.getAttribute(RESULTS_ATTR);
// If no results, this must be the first dispatch, so send the REST request(s)
if (results==null)
{
// define results data structures
final Queue<Map<String, String>> resultsQueue = new ConcurrentLinkedQueue<>();
request.setAttribute(RESULTS_ATTR, results=resultsQueue);
// suspend the request
// This is done before scheduling async handling to avoid race of
// dispatch before startAsync!
final AsyncContext async = request.startAsync();
async.setTimeout(30000);
// extract keywords to search for
String[] keywords=sanitize(request.getParameter(ITEMS_PARAM)).split(",");
final AtomicInteger outstanding=new AtomicInteger(keywords.length);
// Send request each keyword
for (final String item:keywords)
{
_client.newRequest(restURL(item)).method(HttpMethod.GET).send(
new AsyncRestRequest()
{
@Override
void onAuctionFound(Map<String,String> auction)
{
resultsQueue.add(auction);
}
@Override
void onComplete()
{
if (outstanding.decrementAndGet()<=0)
async.dispatch();
}
});
}
// save timing info and return
request.setAttribute(START_ATTR, start);
request.setAttribute(DURATION_ATTR, System.nanoTime() - start);
return;
}
// We have results!
// Generate the response
String thumbs = generateThumbs(results);
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html><head>");
out.println(STYLE);
out.println("</head><body><small>");
long initial = (Long) request.getAttribute(DURATION_ATTR);
long start0 = (Long) request.getAttribute(START_ATTR);
long now = System.nanoTime();
long total=now-start0;
long generate=now-start;
long thread=initial+generate;
out.print("<b>Asynchronous: "+sanitize(request.getParameter(ITEMS_PARAM))+"</b><br/>");
out.print("Total Time: "+ms(total)+"ms<br/>");
out.print("Thread held (<span class='red'>red</span>): "+ms(thread)+"ms (" + ms(initial) + " initial + " + ms(generate) + " generate )<br/>");
out.print("Async wait (<span class='green'>green</span>): "+ms(total-thread)+"ms<br/>");
out.println("<img border='0px' src='asyncrest/red.png' height='20px' width='"+width(initial)+"px'>"+
"<img border='0px' src='asyncrest/green.png' height='20px' width='"+width(total-thread)+"px'>"+
"<img border='0px' src='asyncrest/red.png' height='20px' width='"+width(generate)+"px'>");
out.println("<hr />");
out.println(thumbs);
out.println("</small>");
out.println("</body></html>");
out.close();
}
private abstract class AsyncRestRequest extends Response.Listener.Adapter
{
final Utf8StringBuilder _content = new Utf8StringBuilder();
AsyncRestRequest()
{
}
@Override
public void onContent(Response response, ByteBuffer content)
{
byte[] bytes = BufferUtil.toArray(content);
_content.append(bytes,0,bytes.length);
}
@Override
public void onComplete(Result result)
{
// extract auctions from the results
Map<String,?> query = (Map<String,?>) JSON.parse(_content.toString());
Object[] auctions = (Object[]) query.get("Item");
if (auctions != null)
{
for (Object o : auctions)
onAuctionFound((Map<String,String>)o);
}
onComplete();
}
abstract void onAuctionFound(Map<String,String> details);
abstract void onComplete();
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
doGet(request, response);
}
}