| // |
| // ======================================================================== |
| // Copyright (c) 1995-2016 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); |
| } |
| } |