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