View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 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.util.Map;
24  import java.util.Queue;
25  import java.util.concurrent.ConcurrentLinkedQueue;
26  import java.util.concurrent.atomic.AtomicInteger;
27  
28  import javax.servlet.AsyncContext;
29  import javax.servlet.ServletConfig;
30  import javax.servlet.ServletException;
31  import javax.servlet.http.HttpServletRequest;
32  import javax.servlet.http.HttpServletResponse;
33  
34  import org.eclipse.jetty.client.ContentExchange;
35  import org.eclipse.jetty.client.HttpClient;
36  import org.eclipse.jetty.util.ajax.JSON;
37  
38  /**
39   * Servlet implementation class AsyncRESTServlet.
40   * Enquires ebay REST service for auctions by key word.
41   * May be configured with init parameters: <dl>
42   * <dt>appid</dt><dd>The eBay application ID to use</dd>
43   * </dl>
44   * Each request examines the following request parameters:<dl>
45   * <dt>items</dt><dd>The keyword to search for</dd>
46   * </dl>
47   */
48  public class AsyncRestServlet extends AbstractRestServlet
49  {
50      final static String RESULTS_ATTR = "org.eclipse.jetty.demo.client";
51      final static String DURATION_ATTR = "org.eclipse.jetty.demo.duration";
52      final static String START_ATTR = "org.eclispe.jetty.demo.start";
53  
54      HttpClient _client;
55  
56      public void init(ServletConfig servletConfig) throws ServletException
57      {
58          super.init(servletConfig);
59          
60          _client = new HttpClient();
61          _client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
62  
63          try
64          {
65              _client.start();
66          }
67          catch (Exception e)
68          {
69              throw new ServletException(e);
70          }
71      }
72  
73      protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
74      {
75          Long start=System.nanoTime();
76          
77          // Do we have results yet?
78          Queue<Map<String, String>> results = (Queue<Map<String, String>>) request.getAttribute(RESULTS_ATTR);
79          
80          // If no results, this must be the first dispatch, so send the REST request(s)
81          if (results==null)
82          {
83              // define results data structures
84              final Queue<Map<String, String>> resultsQueue = new ConcurrentLinkedQueue<Map<String,String>>();
85              request.setAttribute(RESULTS_ATTR, results=resultsQueue);
86              
87              // suspend the request
88              // This is done before scheduling async handling to avoid race of 
89              // dispatch before startAsync!
90              final AsyncContext async = request.startAsync();
91              async.setTimeout(30000);
92  
93              // extract keywords to search for
94              String[] keywords=request.getParameter(ITEMS_PARAM).split(",");
95              final AtomicInteger outstanding=new AtomicInteger(keywords.length);
96              
97              // Send request each keyword
98              for (final String item:keywords)
99              {
100                 _client.send(
101                         new AsyncRestRequest(item)
102                         {
103                             void onAuctionFound(Map<String,String> auction)
104                             {
105                                 resultsQueue.add(auction);
106                             }
107                             void onComplete()
108                             {
109                                 if (outstanding.decrementAndGet()<=0)
110                                     async.dispatch();
111                             }
112                         });
113             }
114             
115             // save timing info and return
116             request.setAttribute(START_ATTR, start);
117             request.setAttribute(DURATION_ATTR, new Long(System.nanoTime() - start));
118 
119             return;
120         }
121 
122         // We have results!
123         
124         // Generate the response
125         String thumbs = generateThumbs(results);
126         
127         response.setContentType("text/html");
128         PrintWriter out = response.getWriter();
129         out.println("<html><head>");
130         out.println(STYLE);
131         out.println("</head><body><small>");
132 
133         long initial = (Long) request.getAttribute(DURATION_ATTR);
134         long start0 = (Long) request.getAttribute(START_ATTR);
135 
136         long now = System.nanoTime();
137         long total=now-start0;
138         long generate=now-start;
139         long thread=initial+generate;
140         
141         out.print("<b>Asynchronous: "+request.getParameter(ITEMS_PARAM)+"</b><br/>");
142         out.print("Total Time: "+ms(total)+"ms<br/>");
143 
144         out.print("Thread held (<span class='red'>red</span>): "+ms(thread)+"ms (" + ms(initial) + " initial + " + ms(generate) + " generate )<br/>");
145         out.print("Async wait (<span class='green'>green</span>): "+ms(total-thread)+"ms<br/>");
146         
147         out.println("<img border='0px' src='asyncrest/red.png'   height='20px' width='"+width(initial)+"px'>"+
148                     "<img border='0px' src='asyncrest/green.png' height='20px' width='"+width(total-thread)+"px'>"+
149                     "<img border='0px' src='asyncrest/red.png'   height='20px' width='"+width(generate)+"px'>");
150         
151         out.println("<hr />");
152         out.println(thumbs);
153         out.println("</small>");
154         out.println("</body></html>");
155         out.close();
156     }
157 
158     private abstract class AsyncRestRequest extends ContentExchange
159     {
160         AsyncRestRequest(final String item)
161         {
162             // send the exchange
163             setMethod("GET");
164             setURL(restURL(item));   
165         }
166         
167         abstract void onAuctionFound(Map<String,String> details);
168         abstract void onComplete();
169         
170         protected void onResponseComplete() throws IOException
171         {
172             // extract auctions from the results
173             Map<String,?> query = (Map<String,?>) JSON.parse(this.getResponseContent());
174             Object[] auctions = (Object[]) query.get("Item");
175             if (auctions != null)
176             {
177                 for (Object o : auctions)
178                     onAuctionFound((Map<String,String>)o);
179             }
180 
181             onComplete();
182         }
183 
184         /* ------------------------------------------------------------ */
185         protected void onConnectionFailed(Throwable ex)
186         {
187             getServletContext().log("onConnectionFailed: ",ex);
188             onComplete();
189         }
190 
191         /* ------------------------------------------------------------ */
192         protected void onException(Throwable ex)
193         {
194             getServletContext().log("onConnectionFailed: ",ex);
195             onComplete();
196         }
197 
198         /* ------------------------------------------------------------ */
199         protected void onExpire()
200         {
201             getServletContext().log("onConnectionFailed: expired");
202             onComplete();
203         }
204     }
205 
206     protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
207     {
208         doGet(request, response);
209     }
210 
211 }