View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.example.asyncrest;
20  
21  import java.io.IOException;
22  import java.io.PrintWriter;
23  import java.nio.ByteBuffer;
24  import java.util.Map;
25  import java.util.Queue;
26  import java.util.concurrent.ConcurrentLinkedQueue;
27  import java.util.concurrent.atomic.AtomicInteger;
28  
29  import javax.servlet.AsyncContext;
30  import javax.servlet.ServletConfig;
31  import javax.servlet.ServletException;
32  import javax.servlet.http.HttpServletRequest;
33  import javax.servlet.http.HttpServletResponse;
34  
35  import org.eclipse.jetty.client.HttpClient;
36  import org.eclipse.jetty.client.api.Response;
37  import org.eclipse.jetty.client.api.Result;
38  import org.eclipse.jetty.http.HttpMethod;
39  import org.eclipse.jetty.util.BufferUtil;
40  import org.eclipse.jetty.util.Utf8StringBuilder;
41  import org.eclipse.jetty.util.ajax.JSON;
42  
43  /**
44   * Servlet implementation class AsyncRESTServlet.
45   * Enquires ebay REST service for auctions by key word.
46   * May be configured with init parameters: <dl>
47   * <dt>appid</dt><dd>The eBay application ID to use</dd>
48   * </dl>
49   * Each request examines the following request parameters:<dl>
50   * <dt>items</dt><dd>The keyword to search for</dd>
51   * </dl>
52   */
53  public class AsyncRestServlet extends AbstractRestServlet
54  {
55      final static String RESULTS_ATTR = "org.eclipse.jetty.demo.client";
56      final static String DURATION_ATTR = "org.eclipse.jetty.demo.duration";
57      final static String START_ATTR = "org.eclispe.jetty.demo.start";
58  
59      HttpClient _client;
60  
61      @Override
62      public void init(ServletConfig servletConfig) throws ServletException
63      {
64          super.init(servletConfig);
65  
66          _client = new HttpClient();
67  
68          try
69          {
70              _client.start();
71          }
72          catch (Exception e)
73          {
74              throw new ServletException(e);
75          }
76      }
77  
78      @Override
79      protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
80      {
81          Long start=System.nanoTime();
82  
83          // Do we have results yet?
84          Queue<Map<String, String>> results = (Queue<Map<String, String>>) request.getAttribute(RESULTS_ATTR);
85  
86          // If no results, this must be the first dispatch, so send the REST request(s)
87          if (results==null)
88          {
89              // define results data structures
90              final Queue<Map<String, String>> resultsQueue = new ConcurrentLinkedQueue<>();
91              request.setAttribute(RESULTS_ATTR, results=resultsQueue);
92  
93              // suspend the request
94              // This is done before scheduling async handling to avoid race of
95              // dispatch before startAsync!
96              final AsyncContext async = request.startAsync();
97              async.setTimeout(30000);
98  
99              // extract keywords to search for
100             String[] keywords=sanitize(request.getParameter(ITEMS_PARAM)).split(",");
101             final AtomicInteger outstanding=new AtomicInteger(keywords.length);
102 
103             // Send request each keyword
104             for (final String item:keywords)
105             {
106                 _client.newRequest(restURL(item)).method(HttpMethod.GET).send(
107                     new AsyncRestRequest()
108                     {
109                         @Override
110                         void onAuctionFound(Map<String,String> auction)
111                         {
112                             resultsQueue.add(auction);
113                         }
114                         @Override
115                         void onComplete()
116                         {
117                             if (outstanding.decrementAndGet()<=0)
118                                 async.dispatch();
119                         }
120                     });
121             }
122 
123             // save timing info and return
124             request.setAttribute(START_ATTR, start);
125             request.setAttribute(DURATION_ATTR, System.nanoTime() - start);
126 
127             return;
128         }
129 
130         // We have results!
131 
132         // Generate the response
133         String thumbs = generateThumbs(results);
134 
135         response.setContentType("text/html");
136         PrintWriter out = response.getWriter();
137         out.println("<html><head>");
138         out.println(STYLE);
139         out.println("</head><body><small>");
140 
141         long initial = (Long) request.getAttribute(DURATION_ATTR);
142         long start0 = (Long) request.getAttribute(START_ATTR);
143 
144         long now = System.nanoTime();
145         long total=now-start0;
146         long generate=now-start;
147         long thread=initial+generate;
148 
149         out.print("<b>Asynchronous: "+sanitize(request.getParameter(ITEMS_PARAM))+"</b><br/>");
150         out.print("Total Time: "+ms(total)+"ms<br/>");
151 
152         out.print("Thread held (<span class='red'>red</span>): "+ms(thread)+"ms (" + ms(initial) + " initial + " + ms(generate) + " generate )<br/>");
153         out.print("Async wait (<span class='green'>green</span>): "+ms(total-thread)+"ms<br/>");
154 
155         out.println("<img border='0px' src='asyncrest/red.png'   height='20px' width='"+width(initial)+"px'>"+
156                     "<img border='0px' src='asyncrest/green.png' height='20px' width='"+width(total-thread)+"px'>"+
157                     "<img border='0px' src='asyncrest/red.png'   height='20px' width='"+width(generate)+"px'>");
158 
159         out.println("<hr />");
160         out.println(thumbs);
161         out.println("</small>");
162         out.println("</body></html>");
163         out.close();
164     }
165     
166     private abstract class AsyncRestRequest extends Response.Listener.Adapter
167     {
168         final Utf8StringBuilder _content = new Utf8StringBuilder();
169 
170         AsyncRestRequest()
171         {
172         }
173 
174         @Override
175         public void onContent(Response response, ByteBuffer content)
176         {
177             byte[] bytes = BufferUtil.toArray(content);
178             _content.append(bytes,0,bytes.length);
179         }
180 
181         @Override
182         public void onComplete(Result result)
183         {
184             // extract auctions from the results
185             Map<String,?> query = (Map<String,?>) JSON.parse(_content.toString());
186             Object[] auctions = (Object[]) query.get("Item");
187             if (auctions != null)
188             {
189                 for (Object o : auctions)
190                     onAuctionFound((Map<String,String>)o);
191             }
192             onComplete();
193 
194         }
195 
196         abstract void onAuctionFound(Map<String,String> details);
197         abstract void onComplete();
198 
199     }
200 
201     @Override
202     protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
203     {
204         doGet(request, response);
205     }
206 }