• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.testutils
18 
19 import android.net.Uri
20 import com.android.net.module.util.ArrayTrackRecord
21 import fi.iki.elonen.NanoHTTPD
22 import java.io.IOException
23 
24 /**
25  * A minimal HTTP server running on a random available port.
26  *
27  * @param host The host to listen to, or null to listen on all hosts
28  * @param port The port to listen to, or 0 to auto select
29  */
30 class TestHttpServer
31     @JvmOverloads constructor(host: String? = null, port: Int = 0) : NanoHTTPD(host, port) {
32     // Map of URL path -> HTTP response code
33     private val responses = HashMap<Request, Response>()
34 
35     /**
36      * A record of all requests received by the server since it was started.
37      */
38     val requestsRecord = ArrayTrackRecord<Request>()
39 
40     /**
41      * A request received by the test server.
42      */
43     data class Request(
44         val path: String,
45         val method: Method = Method.GET,
46         val queryParameters: String = ""
47     ) {
48         /**
49          * Returns whether the specified [Uri] matches parameters of this request.
50          */
51         fun matches(uri: Uri) = (uri.path ?: "") == path && (uri.query ?: "") == queryParameters
52     }
53 
54     /**
55      * Add a response for GET requests with the path and query parameters of the specified [Uri].
56      */
57     fun addResponse(
58         uri: Uri,
59         statusCode: Response.IStatus,
60         headers: Map<String, String>? = null,
61         content: String = ""
62     ) {
63         addResponse(Request(uri.path
64                 ?: "", Method.GET, uri.query ?: ""),
65                 statusCode, headers, content)
66     }
67 
68     /**
69      * Add a response for the given request.
70      */
71     fun addResponse(
72         request: Request,
73         statusCode: Response.IStatus,
74         headers: Map<String, String>? = null,
75         content: String = ""
76     ) {
77         val response = newFixedLengthResponse(statusCode, "text/plain", content)
78         headers?.forEach {
79             (key, value) -> response.addHeader(key, value)
80         }
81         responses[request] = response
82     }
83 
84     override fun serve(session: IHTTPSession): Response {
85         val request = Request(session.uri
86                 ?: "", session.method, session.queryParameterString ?: "")
87         requestsRecord.add(request)
88 
89         // For PUT and POST, call parseBody to read InputStream before responding.
90         if (Method.PUT == session.method || Method.POST == session.method) {
91             try {
92                 session.parseBody(HashMap())
93             } catch (e: Exception) {
94                 when (e) {
95                     is IOException, is ResponseException -> e.toResponse()
96                     else -> throw e
97                 }
98             }
99         }
100 
101         // Default response is a 404
102         return responses[request] ?: super.serve(session)
103     }
104 
105     fun Exception.toResponse() =
106         newFixedLengthResponse(Response.Status.INTERNAL_ERROR, "text/plain", this.toString())
107 }
108