Upstream version 10.39.233.0
[platform/framework/web/crosswalk.git] / src / net / test / android / javatests / src / org / chromium / net / test / util / TestWebServer.java
1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.net.test.util;
6
7 import android.util.Base64;
8 import android.util.Log;
9 import android.util.Pair;
10
11 import org.apache.http.HttpException;
12 import org.apache.http.HttpRequest;
13 import org.apache.http.HttpResponse;
14 import org.apache.http.HttpStatus;
15 import org.apache.http.HttpVersion;
16 import org.apache.http.RequestLine;
17 import org.apache.http.StatusLine;
18 import org.apache.http.entity.ByteArrayEntity;
19 import org.apache.http.impl.DefaultHttpServerConnection;
20 import org.apache.http.impl.cookie.DateUtils;
21 import org.apache.http.message.BasicHttpResponse;
22 import org.apache.http.params.BasicHttpParams;
23 import org.apache.http.params.CoreProtocolPNames;
24 import org.apache.http.params.HttpParams;
25
26 import java.io.ByteArrayInputStream;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.net.MalformedURLException;
30 import java.net.ServerSocket;
31 import java.net.Socket;
32 import java.net.URI;
33 import java.net.URL;
34 import java.net.URLConnection;
35 import java.security.KeyManagementException;
36 import java.security.KeyStore;
37 import java.security.NoSuchAlgorithmException;
38 import java.security.cert.X509Certificate;
39 import java.util.ArrayList;
40 import java.util.Date;
41 import java.util.HashMap;
42 import java.util.Hashtable;
43 import java.util.List;
44 import java.util.Map;
45
46 import javax.net.ssl.HostnameVerifier;
47 import javax.net.ssl.HttpsURLConnection;
48 import javax.net.ssl.KeyManager;
49 import javax.net.ssl.KeyManagerFactory;
50 import javax.net.ssl.SSLContext;
51 import javax.net.ssl.SSLSession;
52 import javax.net.ssl.X509TrustManager;
53
54 /**
55  * Simple http test server for testing.
56  *
57  * This server runs in a thread in the current process, so it is convenient
58  * for loopback testing without the need to setup tcp forwarding to the
59  * host computer.
60  *
61  * Based heavily on the CTSWebServer in Android.
62  */
63 public class TestWebServer {
64     private static final String TAG = "TestWebServer";
65
66     public static final String SHUTDOWN_PREFIX = "/shutdown";
67
68     private static TestWebServer sInstance;
69     private static TestWebServer sSecureInstance;
70     private static Hashtable<Integer, String> sReasons;
71
72     private final ServerThread mServerThread;
73     private String mServerUri;
74     private final boolean mSsl;
75     private final int mPort;
76
77     private static class Response {
78         final byte[] mResponseData;
79         final List<Pair<String, String>> mResponseHeaders;
80         final boolean mIsRedirect;
81         final Runnable mResponseAction;
82         final boolean mIsNotFound;
83
84         Response(byte[] responseData, List<Pair<String, String>> responseHeaders,
85                 boolean isRedirect, boolean isNotFound, Runnable responseAction) {
86             mIsRedirect = isRedirect;
87             mIsNotFound = isNotFound;
88             mResponseData = responseData;
89             mResponseHeaders = responseHeaders == null ?
90                     new ArrayList<Pair<String, String>>() : responseHeaders;
91             mResponseAction = responseAction;
92         }
93     }
94
95     // The Maps below are modified on both the client thread and the internal server thread, so
96     // need to use a lock when accessing them.
97     private final Object mLock = new Object();
98     private final Map<String, Response> mResponseMap = new HashMap<String, Response>();
99     private final Map<String, Integer> mResponseCountMap = new HashMap<String, Integer>();
100     private final Map<String, HttpRequest> mLastRequestMap = new HashMap<String, HttpRequest>();
101
102     /**
103      * Create and start a local HTTP server instance.
104      * @param port Port number the server must use, or 0 to automatically choose a free port.
105      * @param ssl True if the server should be using secure sockets.
106      * @throws Exception
107      */
108     private TestWebServer(int port, boolean ssl) throws Exception {
109         mPort = port;
110
111         mSsl = ssl;
112         if (mSsl) {
113             mServerUri = "https:";
114             if (sSecureInstance != null) {
115                 sSecureInstance.shutdown();
116             }
117         } else {
118             mServerUri = "http:";
119             if (sInstance != null) {
120                 sInstance.shutdown();
121             }
122         }
123
124         mServerThread = new ServerThread(this, mPort, mSsl);
125         mServerUri += "//localhost:" + mServerThread.mSocket.getLocalPort();
126     }
127
128     public static TestWebServer start(int port) throws Exception {
129         if (sInstance != null) {
130             throw new IllegalStateException("Tried to start multiple TestWebServers");
131         }
132
133         TestWebServer server = new TestWebServer(port, false);
134         server.mServerThread.start();
135         setInstance(server);
136         return server;
137     }
138
139     public static TestWebServer start() throws Exception {
140         return start(0);
141     }
142
143     public static TestWebServer startSsl(int port) throws Exception {
144         if (sSecureInstance != null) {
145             throw new IllegalStateException("Tried to start multiple SSL TestWebServers");
146         }
147
148         TestWebServer server = new TestWebServer(port, true);
149         server.mServerThread.start();
150         setSecureInstance(server);
151         return server;
152     }
153
154     public static TestWebServer startSsl() throws Exception {
155         return startSsl(0);
156     }
157
158     /**
159      * Terminate the http server.
160      */
161     public void shutdown() {
162         if (mSsl) {
163             setSecureInstance(null);
164         } else {
165             setInstance(null);
166         }
167
168         try {
169             // Avoid a deadlock between two threads where one is trying to call
170             // close() and the other one is calling accept() by sending a GET
171             // request for shutdown and having the server's one thread
172             // sequentially call accept() and close().
173             URL url = new URL(mServerUri + SHUTDOWN_PREFIX);
174             URLConnection connection = openConnection(url);
175             connection.connect();
176
177             // Read the input from the stream to send the request.
178             InputStream is = connection.getInputStream();
179             is.close();
180
181             // Block until the server thread is done shutting down.
182             mServerThread.join();
183
184         } catch (MalformedURLException e) {
185             throw new IllegalStateException(e);
186         } catch (InterruptedException e) {
187             throw new RuntimeException(e);
188         } catch (IOException e) {
189             throw new RuntimeException(e);
190         } catch (NoSuchAlgorithmException e) {
191             throw new IllegalStateException(e);
192         } catch (KeyManagementException e) {
193             throw new IllegalStateException(e);
194         }
195     }
196
197     // Setting static variables from instance methods causes findbugs warnings. Calling static
198     // methods which set static variables from instance methods isn't any better, but it silences
199     // the warnings.
200     private static void setInstance(TestWebServer instance) {
201         sInstance = instance;
202     }
203
204     private static void setSecureInstance(TestWebServer instance) {
205         sSecureInstance = instance;
206     }
207
208     private static final int RESPONSE_STATUS_NORMAL = 0;
209     private static final int RESPONSE_STATUS_MOVED_TEMPORARILY = 1;
210     private static final int RESPONSE_STATUS_NOT_FOUND = 2;
211
212     private String setResponseInternal(
213             String requestPath, byte[] responseData,
214             List<Pair<String, String>> responseHeaders, Runnable responseAction,
215             int status) {
216         final boolean isRedirect = (status == RESPONSE_STATUS_MOVED_TEMPORARILY);
217         final boolean isNotFound = (status == RESPONSE_STATUS_NOT_FOUND);
218
219         synchronized (mLock) {
220             mResponseMap.put(requestPath, new Response(
221                     responseData, responseHeaders, isRedirect, isNotFound, responseAction));
222             mResponseCountMap.put(requestPath, Integer.valueOf(0));
223             mLastRequestMap.put(requestPath, null);
224         }
225         return getResponseUrl(requestPath);
226     }
227
228     /**
229      * Gets the URL on the server under which a particular request path will be accessible.
230      *
231      * This only gets the URL, you still need to set the response if you intend to access it.
232      *
233      * @param requestPath The path to respond to.
234      * @return The full URL including the requestPath.
235      */
236     public String getResponseUrl(String requestPath) {
237         return mServerUri + requestPath;
238     }
239
240     /**
241      * Sets a 404 (not found) response to be returned when a particular request path is passed in.
242      *
243      * @param requestPath The path to respond to.
244      * @return The full URL including the path that should be requested to get the expected
245      *         response.
246      */
247     public String setResponseWithNotFoundStatus(
248             String requestPath) {
249         return setResponseInternal(requestPath, "".getBytes(), null, null,
250                 RESPONSE_STATUS_NOT_FOUND);
251     }
252
253     /**
254      * Sets a response to be returned when a particular request path is passed
255      * in (with the option to specify additional headers).
256      *
257      * @param requestPath The path to respond to.
258      * @param responseString The response body that will be returned.
259      * @param responseHeaders Any additional headers that should be returned along with the
260      *                        response (null is acceptable).
261      * @return The full URL including the path that should be requested to get the expected
262      *         response.
263      */
264     public String setResponse(
265             String requestPath, String responseString,
266             List<Pair<String, String>> responseHeaders) {
267         return setResponseInternal(requestPath, responseString.getBytes(), responseHeaders, null,
268                 RESPONSE_STATUS_NORMAL);
269     }
270
271     /**
272      * Sets a response to be returned when a particular request path is passed
273      * in with the option to specify additional headers as well as an arbitrary action to be
274      * executed on each request.
275      *
276      * @param requestPath The path to respond to.
277      * @param responseString The response body that will be returned.
278      * @param responseHeaders Any additional headers that should be returned along with the
279      *                        response (null is acceptable).
280      * @param responseAction The action to be performed when fetching the response.  This action
281      *                       will be executed for each request and will be handled on a background
282      *                       thread.
283      * @return The full URL including the path that should be requested to get the expected
284      *         response.
285      */
286     public String setResponseWithRunnableAction(
287             String requestPath, String responseString, List<Pair<String, String>> responseHeaders,
288             Runnable responseAction) {
289         return setResponseInternal(
290                 requestPath, responseString.getBytes(), responseHeaders, responseAction,
291                 RESPONSE_STATUS_NORMAL);
292     }
293
294     /**
295      * Sets a redirect.
296      *
297      * @param requestPath The path to respond to.
298      * @param targetPath The path to redirect to.
299      * @return The full URL including the path that should be requested to get the expected
300      *         response.
301      */
302     public String setRedirect(
303             String requestPath, String targetPath) {
304         List<Pair<String, String>> responseHeaders = new ArrayList<Pair<String, String>>();
305         responseHeaders.add(Pair.create("Location", targetPath));
306
307         return setResponseInternal(requestPath, targetPath.getBytes(), responseHeaders, null,
308                 RESPONSE_STATUS_MOVED_TEMPORARILY);
309     }
310
311     /**
312      * Sets a base64 encoded response to be returned when a particular request path is passed
313      * in (with the option to specify additional headers).
314      *
315      * @param requestPath The path to respond to.
316      * @param base64EncodedResponse The response body that is base64 encoded. The actual server
317      *                              response will the decoded binary form.
318      * @param responseHeaders Any additional headers that should be returned along with the
319      *                        response (null is acceptable).
320      * @return The full URL including the path that should be requested to get the expected
321      *         response.
322      */
323     public String setResponseBase64(
324             String requestPath, String base64EncodedResponse,
325             List<Pair<String, String>> responseHeaders) {
326         return setResponseInternal(
327                 requestPath, Base64.decode(base64EncodedResponse, Base64.DEFAULT),
328                 responseHeaders, null, RESPONSE_STATUS_NORMAL);
329     }
330
331     /**
332      * Get the number of requests was made at this path since it was last set.
333      */
334     public int getRequestCount(String requestPath) {
335         Integer count = null;
336         synchronized (mLock) {
337             count = mResponseCountMap.get(requestPath);
338         }
339         if (count == null) throw new IllegalArgumentException("Path not set: " + requestPath);
340         return count.intValue();
341     }
342
343     /**
344      * Returns the last HttpRequest at this path. Can return null if it is never requested.
345      */
346     public HttpRequest getLastRequest(String requestPath) {
347         synchronized (mLock) {
348             if (!mLastRequestMap.containsKey(requestPath))
349                 throw new IllegalArgumentException("Path not set: " + requestPath);
350             return mLastRequestMap.get(requestPath);
351         }
352     }
353
354     public String getBaseUrl() {
355         return mServerUri + "/";
356     }
357
358     private URLConnection openConnection(URL url)
359             throws IOException, NoSuchAlgorithmException, KeyManagementException {
360         if (mSsl) {
361             // Install hostname verifiers and trust managers that don't do
362             // anything in order to get around the client not trusting
363             // the test server due to a lack of certificates.
364
365             HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
366             connection.setHostnameVerifier(new TestHostnameVerifier());
367
368             SSLContext context = SSLContext.getInstance("TLS");
369             TestTrustManager trustManager = new TestTrustManager();
370             context.init(null, new TestTrustManager[] {trustManager}, null);
371             connection.setSSLSocketFactory(context.getSocketFactory());
372
373             return connection;
374         } else {
375             return url.openConnection();
376         }
377     }
378
379     /**
380      * {@link X509TrustManager} that trusts everybody. This is used so that
381      * the client calling {@link TestWebServer#shutdown()} can issue a request
382      * for shutdown by blindly trusting the {@link TestWebServer}'s
383      * credentials.
384      */
385     private static class TestTrustManager implements X509TrustManager {
386         @Override
387         public void checkClientTrusted(X509Certificate[] chain, String authType) {
388             // Trust the TestWebServer...
389         }
390
391         @Override
392         public void checkServerTrusted(X509Certificate[] chain, String authType) {
393             // Trust the TestWebServer...
394         }
395
396         @Override
397         public X509Certificate[] getAcceptedIssuers() {
398             return null;
399         }
400     }
401
402     /**
403      * {@link HostnameVerifier} that verifies everybody. This permits
404      * the client to trust the web server and call
405      * {@link TestWebServer#shutdown()}.
406      */
407     private static class TestHostnameVerifier implements HostnameVerifier {
408         @Override
409         public boolean verify(String hostname, SSLSession session) {
410             return true;
411         }
412     }
413
414     private void servedResponseFor(String path, HttpRequest request) {
415         synchronized (mLock) {
416             mResponseCountMap.put(path, Integer.valueOf(
417                     mResponseCountMap.get(path).intValue() + 1));
418             mLastRequestMap.put(path, request);
419         }
420     }
421
422     /**
423      * Generate a response to the given request.
424      *
425      * <p>Always executed on the background server thread.
426      *
427      * <p>If there is an action associated with the response, it will be executed inside of
428      * this function.
429      *
430      * @throws InterruptedException
431      */
432     private HttpResponse getResponse(HttpRequest request) throws InterruptedException {
433         assert Thread.currentThread() == mServerThread
434                 : "getResponse called from non-server thread";
435
436         RequestLine requestLine = request.getRequestLine();
437         HttpResponse httpResponse = null;
438         Log.i(TAG, requestLine.getMethod() + ": " + requestLine.getUri());
439         String uriString = requestLine.getUri();
440         URI uri = URI.create(uriString);
441         String path = uri.getPath();
442
443         Response response = null;
444         synchronized (mLock) {
445             response = mResponseMap.get(path);
446         }
447         if (path.equals(SHUTDOWN_PREFIX)) {
448             httpResponse = createResponse(HttpStatus.SC_OK);
449         } else if (response == null) {
450             httpResponse = createResponse(HttpStatus.SC_NOT_FOUND);
451         } else if (response.mIsNotFound) {
452             httpResponse = createResponse(HttpStatus.SC_NOT_FOUND);
453             servedResponseFor(path, request);
454         } else if (response.mIsRedirect) {
455             httpResponse = createResponse(HttpStatus.SC_MOVED_TEMPORARILY);
456             for (Pair<String, String> header : response.mResponseHeaders) {
457                 httpResponse.addHeader(header.first, header.second);
458             }
459             servedResponseFor(path, request);
460         } else {
461             if (response.mResponseAction != null) response.mResponseAction.run();
462
463             httpResponse = createResponse(HttpStatus.SC_OK);
464             ByteArrayEntity entity = createEntity(response.mResponseData);
465             httpResponse.setEntity(entity);
466             httpResponse.setHeader("Content-Length", "" + entity.getContentLength());
467             for (Pair<String, String> header : response.mResponseHeaders) {
468                 httpResponse.addHeader(header.first, header.second);
469             }
470             servedResponseFor(path, request);
471         }
472         StatusLine sl = httpResponse.getStatusLine();
473         Log.i(TAG, sl.getStatusCode() + "(" + sl.getReasonPhrase() + ")");
474         setDateHeaders(httpResponse);
475         return httpResponse;
476     }
477
478     private void setDateHeaders(HttpResponse response) {
479         response.addHeader("Date", DateUtils.formatDate(new Date(), DateUtils.PATTERN_RFC1123));
480     }
481
482     /**
483      * Create an empty response with the given status.
484      */
485     private HttpResponse createResponse(int status) {
486         HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_0, status, null);
487         String reason = null;
488
489         // This synchronized silences findbugs.
490         synchronized (TestWebServer.class) {
491             if (sReasons == null) {
492                 sReasons = new Hashtable<Integer, String>();
493                 sReasons.put(HttpStatus.SC_UNAUTHORIZED, "Unauthorized");
494                 sReasons.put(HttpStatus.SC_NOT_FOUND, "Not Found");
495                 sReasons.put(HttpStatus.SC_FORBIDDEN, "Forbidden");
496                 sReasons.put(HttpStatus.SC_MOVED_TEMPORARILY, "Moved Temporarily");
497             }
498             // Fill in error reason. Avoid use of the ReasonPhraseCatalog, which is
499             // Locale-dependent.
500             reason = sReasons.get(status);
501         }
502
503         if (reason != null) {
504             StringBuffer buf = new StringBuffer("<html><head><title>");
505             buf.append(reason);
506             buf.append("</title></head><body>");
507             buf.append(reason);
508             buf.append("</body></html>");
509             ByteArrayEntity entity = createEntity(buf.toString().getBytes());
510             response.setEntity(entity);
511             response.setHeader("Content-Length", "" + entity.getContentLength());
512         }
513         return response;
514     }
515
516     /**
517      * Create a string entity for the given content.
518      */
519     private ByteArrayEntity createEntity(byte[] data) {
520         ByteArrayEntity entity = new ByteArrayEntity(data);
521         entity.setContentType("text/html");
522         return entity;
523     }
524
525     private static class ServerThread extends Thread {
526         private TestWebServer mServer;
527         private ServerSocket mSocket;
528         private boolean mIsSsl;
529         private boolean mIsCancelled;
530         private SSLContext mSslContext;
531
532         /**
533          * Defines the keystore contents for the server, BKS version. Holds just a
534          * single self-generated key. The subject name is "Test Server".
535          */
536         private static final String SERVER_KEYS_BKS =
537             "AAAAAQAAABQDkebzoP1XwqyWKRCJEpn/t8dqIQAABDkEAAVteWtleQAAARpYl20nAAAAAQAFWC41" +
538             "MDkAAAJNMIICSTCCAbKgAwIBAgIESEfU1jANBgkqhkiG9w0BAQUFADBpMQswCQYDVQQGEwJVUzET" +
539             "MBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEBxMDTVRWMQ8wDQYDVQQKEwZHb29nbGUxEDAOBgNV" +
540             "BAsTB0FuZHJvaWQxFDASBgNVBAMTC1Rlc3QgU2VydmVyMB4XDTA4MDYwNTExNTgxNFoXDTA4MDkw" +
541             "MzExNTgxNFowaTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExDDAKBgNVBAcTA01U" +
542             "VjEPMA0GA1UEChMGR29vZ2xlMRAwDgYDVQQLEwdBbmRyb2lkMRQwEgYDVQQDEwtUZXN0IFNlcnZl" +
543             "cjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0LIdKaIr9/vsTq8BZlA3R+NFWRaH4lGsTAQy" +
544             "DPMF9ZqEDOaL6DJuu0colSBBBQ85hQTPa9m9nyJoN3pEi1hgamqOvQIWcXBk+SOpUGRZZFXwniJV" +
545             "zDKU5nE9MYgn2B9AoiH3CSuMz6HRqgVaqtppIe1jhukMc/kHVJvlKRNy9XMCAwEAATANBgkqhkiG" +
546             "9w0BAQUFAAOBgQC7yBmJ9O/eWDGtSH9BH0R3dh2NdST3W9hNZ8hIa8U8klhNHbUCSSktZmZkvbPU" +
547             "hse5LI3dh6RyNDuqDrbYwcqzKbFJaq/jX9kCoeb3vgbQElMRX8D2ID1vRjxwlALFISrtaN4VpWzV" +
548             "yeoHPW4xldeZmoVtjn8zXNzQhLuBqX2MmAAAAqwAAAAUvkUScfw9yCSmALruURNmtBai7kQAAAZx" +
549             "4Jmijxs/l8EBaleaUru6EOPioWkUAEVWCxjM/TxbGHOi2VMsQWqRr/DZ3wsDmtQgw3QTrUK666sR" +
550             "MBnbqdnyCyvM1J2V1xxLXPUeRBmR2CXorYGF9Dye7NkgVdfA+9g9L/0Au6Ugn+2Cj5leoIgkgApN" +
551             "vuEcZegFlNOUPVEs3SlBgUF1BY6OBM0UBHTPwGGxFBBcetcuMRbUnu65vyDG0pslT59qpaR0TMVs" +
552             "P+tcheEzhyjbfM32/vwhnL9dBEgM8qMt0sqF6itNOQU/F4WGkK2Cm2v4CYEyKYw325fEhzTXosck" +
553             "MhbqmcyLab8EPceWF3dweoUT76+jEZx8lV2dapR+CmczQI43tV9btsd1xiBbBHAKvymm9Ep9bPzM" +
554             "J0MQi+OtURL9Lxke/70/MRueqbPeUlOaGvANTmXQD2OnW7PISwJ9lpeLfTG0LcqkoqkbtLKQLYHI" +
555             "rQfV5j0j+wmvmpMxzjN3uvNajLa4zQ8l0Eok9SFaRr2RL0gN8Q2JegfOL4pUiHPsh64WWya2NB7f" +
556             "V+1s65eA5ospXYsShRjo046QhGTmymwXXzdzuxu8IlnTEont6P4+J+GsWk6cldGbl20hctuUKzyx" +
557             "OptjEPOKejV60iDCYGmHbCWAzQ8h5MILV82IclzNViZmzAapeeCnexhpXhWTs+xDEYSKEiG/camt" +
558             "bhmZc3BcyVJrW23PktSfpBQ6D8ZxoMfF0L7V2GQMaUg+3r7ucrx82kpqotjv0xHghNIm95aBr1Qw" +
559             "1gaEjsC/0wGmmBDg1dTDH+F1p9TInzr3EFuYD0YiQ7YlAHq3cPuyGoLXJ5dXYuSBfhDXJSeddUkl" +
560             "k1ufZyOOcskeInQge7jzaRfmKg3U94r+spMEvb0AzDQVOKvjjo1ivxMSgFRZaDb/4qw=";
561
562         private static final String PASSWORD = "android";
563
564         /**
565          * Loads a keystore from a base64-encoded String. Returns the KeyManager[]
566          * for the result.
567          */
568         private KeyManager[] getKeyManagers() throws Exception {
569             byte[] bytes = Base64.decode(SERVER_KEYS_BKS, Base64.DEFAULT);
570             InputStream inputStream = new ByteArrayInputStream(bytes);
571
572             KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
573             keyStore.load(inputStream, PASSWORD.toCharArray());
574             inputStream.close();
575
576             String algorithm = KeyManagerFactory.getDefaultAlgorithm();
577             KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(algorithm);
578             keyManagerFactory.init(keyStore, PASSWORD.toCharArray());
579
580             return keyManagerFactory.getKeyManagers();
581         }
582
583
584         public ServerThread(TestWebServer server, int port, boolean ssl) throws Exception {
585             super("ServerThread");
586             mServer = server;
587             mIsSsl = ssl;
588             int retry = 3;
589             while (true) {
590                 try {
591                     if (mIsSsl) {
592                         mSslContext = SSLContext.getInstance("TLS");
593                         mSslContext.init(getKeyManagers(), null, null);
594                         mSocket = mSslContext.getServerSocketFactory().createServerSocket(port);
595                     } else {
596                         mSocket = new ServerSocket(port);
597                     }
598                     return;
599                 } catch (IOException e) {
600                     Log.w(TAG, e);
601                     if (--retry == 0) {
602                         throw e;
603                     }
604                     // sleep in case server socket is still being closed
605                     Thread.sleep(1000);
606                 }
607             }
608         }
609
610         @Override
611         public void run() {
612             HttpParams params = new BasicHttpParams();
613             params.setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_0);
614             while (!mIsCancelled) {
615                 try {
616                     Socket socket = mSocket.accept();
617                     DefaultHttpServerConnection conn = new DefaultHttpServerConnection();
618                     conn.bind(socket, params);
619
620                     // Determine whether we need to shutdown early before
621                     // parsing the response since conn.close() will crash
622                     // for SSL requests due to UnsupportedOperationException.
623                     HttpRequest request = conn.receiveRequestHeader();
624                     if (isShutdownRequest(request)) {
625                         mIsCancelled = true;
626                     }
627
628                     HttpResponse response = mServer.getResponse(request);
629                     conn.sendResponseHeader(response);
630                     conn.sendResponseEntity(response);
631                     conn.close();
632
633                 } catch (IOException e) {
634                     // normal during shutdown, ignore
635                     Log.w(TAG, e);
636                 } catch (HttpException e) {
637                     Log.w(TAG, e);
638                 } catch (InterruptedException e) {
639                     Log.w(TAG, e);
640                 } catch (UnsupportedOperationException e) {
641                     // DefaultHttpServerConnection's close() throws an
642                     // UnsupportedOperationException.
643                     Log.w(TAG, e);
644                 }
645             }
646             try {
647                 mSocket.close();
648             } catch (IOException ignored) {
649                 // safe to ignore
650             }
651         }
652
653         private boolean isShutdownRequest(HttpRequest request) {
654             RequestLine requestLine = request.getRequestLine();
655             String uriString = requestLine.getUri();
656             URI uri = URI.create(uriString);
657             String path = uri.getPath();
658             return path.equals(SHUTDOWN_PREFIX);
659         }
660     }
661 }