1 /*
2  * 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 
23 /**
24  * A minimal HTTP server running on a random available port.
25  *
26  * @param host The host to listen to, or null to listen on all hosts
27  */
28 class TestHttpServer(host: String? = null) : NanoHTTPD(host, 0 /* auto-select the port */) {
29     // Map of URL path -> HTTP response code
30     private val responses = HashMap<Request, Response>()
31 
32     /**
33      * A record of all requests received by the server since it was started.
34      */
35     val requestsRecord = ArrayTrackRecord<Request>()
36 
37     /**
38      * A request received by the test server.
39      */
40     data class Request(
41         val path: String,
42         val method: Method = Method.GET,
43         val queryParameters: String = ""
44     ) {
45         /**
46          * Returns whether the specified [Uri] matches parameters of this request.
47          */
48         fun matches(uri: Uri) = (uri.path ?: "") == path && (uri.query ?: "") == queryParameters
49     }
50 
51     /**
52      * Add a response for GET requests with the path and query parameters of the specified [Uri].
53      */
54     fun addResponse(
55         uri: Uri,
56         statusCode: Response.IStatus,
57         headers: Map<String, String>? = null,
58         content: String = ""
59     ) {
60         addResponse(Request(uri.path
61                 ?: "", Method.GET, uri.query ?: ""),
62                 statusCode, headers, content)
63     }
64 
65     /**
66      * Add a response for the given request.
67      */
68     fun addResponse(
69         request: Request,
70         statusCode: Response.IStatus,
71         headers: Map<String, String>? = null,
72         content: String = ""
73     ) {
74         val response = newFixedLengthResponse(statusCode, "text/plain", content)
75         headers?.forEach {
76             (key, value) -> response.addHeader(key, value)
77         }
78         responses[request] = response
79     }
80 
81     override fun serve(session: IHTTPSession): Response {
82         val request = Request(session.uri
83                 ?: "", session.method, session.queryParameterString ?: "")
84         requestsRecord.add(request)
85         // Default response is a 404
86         return responses[request] ?: super.serve(session)
87     }
88 }