1 // Copyright 2014 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.
5 #include "native_test_server.h"
9 #include "base/android/jni_android.h"
10 #include "base/android/jni_string.h"
11 #include "base/android/path_utils.h"
12 #include "base/bind.h"
13 #include "base/files/file_path.h"
14 #include "base/files/file_util.h"
15 #include "base/macros.h"
16 #include "base/memory/scoped_ptr.h"
17 #include "base/path_service.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/stringprintf.h"
20 #include "components/cronet/android/cronet_url_request_context_adapter.h"
21 #include "components/cronet/android/url_request_context_adapter.h"
22 #include "jni/NativeTestServer_jni.h"
23 #include "net/base/url_util.h"
24 #include "net/dns/host_resolver_impl.h"
25 #include "net/dns/mock_host_resolver.h"
26 #include "net/http/http_status_code.h"
27 #include "net/test/embedded_test_server/embedded_test_server.h"
28 #include "net/test/embedded_test_server/http_request.h"
29 #include "net/test/embedded_test_server/http_response.h"
36 const char kEchoBodyPath
[] = "/echo_body";
37 const char kEchoHeaderPath
[] = "/echo_header";
38 const char kEchoAllHeadersPath
[] = "/echo_all_headers";
39 const char kEchoMethodPath
[] = "/echo_method";
40 const char kRedirectToEchoBodyPath
[] = "/redirect_to_echo_body";
41 const char kFakeSdchDomain
[] = "fake.sdch.domain";
42 // Path that advertises the dictionary passed in query params if client
43 // supports Sdch encoding. E.g. /sdch/index?q=LeQxM80O will make the server
44 // responds with "Get-Dictionary: /sdch/dict/LeQxM80O".
45 const char kSdchPath
[] = "/sdch/index";
46 // Path that returns encoded response if client has the right dictionary.
47 const char kSdchTestPath
[] = "/sdch/test";
48 // Path where dictionaries are stored.
49 const char kSdchDictPath
[] = "/sdch/dict/";
51 net::test_server::EmbeddedTestServer
* g_test_server
= nullptr;
53 class CustomHttpResponse
: public net::test_server::HttpResponse
{
55 CustomHttpResponse(const std::string
& headers
, const std::string
& contents
)
56 : headers_(headers
), contents_(contents
) {}
58 std::string
ToResponseString() const override
{
59 return headers_
+ "\r\n" + contents_
;
62 void AddHeader(const std::string
& key_value_pair
) {
63 headers_
.append(base::StringPrintf("%s\r\n", key_value_pair
.c_str()));
68 std::string contents_
;
70 DISALLOW_COPY_AND_ASSIGN(CustomHttpResponse
);
73 scoped_ptr
<CustomHttpResponse
> ConstructResponseBasedOnFile(
74 const base::FilePath
& file_path
) {
75 std::string file_contents
;
76 bool read_file
= base::ReadFileToString(file_path
, &file_contents
);
78 base::FilePath
headers_path(
79 file_path
.AddExtension(FILE_PATH_LITERAL("mock-http-headers")));
80 std::string headers_contents
;
81 bool read_headers
= base::ReadFileToString(headers_path
, &headers_contents
);
83 scoped_ptr
<CustomHttpResponse
> http_response(
84 new CustomHttpResponse(headers_contents
, file_contents
));
85 return http_response
.Pass();
88 scoped_ptr
<net::test_server::HttpResponse
> NativeTestServerRequestHandler(
89 const net::test_server::HttpRequest
& request
) {
90 DCHECK(g_test_server
);
91 scoped_ptr
<net::test_server::BasicHttpResponse
> response(
92 new net::test_server::BasicHttpResponse());
93 response
->set_content_type("text/plain");
95 if (request
.relative_url
== kEchoBodyPath
) {
96 if (request
.has_content
) {
97 response
->set_content(request
.content
);
99 response
->set_content("Request has no body. :(");
101 return response
.Pass();
104 if (StartsWithASCII(request
.relative_url
, kEchoHeaderPath
, true)) {
105 GURL url
= g_test_server
->GetURL(request
.relative_url
);
106 auto it
= request
.headers
.find(url
.query());
107 if (it
!= request
.headers
.end()) {
108 response
->set_content(it
->second
);
110 response
->set_content("Header not found. :(");
112 return response
.Pass();
115 if (request
.relative_url
== kEchoAllHeadersPath
) {
116 response
->set_content(request
.all_headers
);
117 return response
.Pass();
120 if (request
.relative_url
== kEchoMethodPath
) {
121 response
->set_content(request
.method_string
);
122 return response
.Pass();
125 if (request
.relative_url
== kRedirectToEchoBodyPath
) {
126 response
->set_code(net::HTTP_TEMPORARY_REDIRECT
);
127 response
->AddCustomHeader("Location", kEchoBodyPath
);
128 return response
.Pass();
131 // Unhandled requests result in the Embedded test server sending a 404.
132 return scoped_ptr
<net::test_server::BasicHttpResponse
>();
135 scoped_ptr
<net::test_server::HttpResponse
> SdchRequestHandler(
136 const net::test_server::HttpRequest
& request
) {
137 DCHECK(g_test_server
);
138 base::FilePath dir_path
;
139 bool get_data_dir
= base::android::GetDataDirectory(&dir_path
);
140 DCHECK(get_data_dir
);
141 dir_path
= dir_path
.Append(FILE_PATH_LITERAL("test"));
143 if (StartsWithASCII(request
.relative_url
, kSdchPath
, true)) {
144 base::FilePath file_path
= dir_path
.Append("sdch/index");
145 scoped_ptr
<CustomHttpResponse
> response
=
146 ConstructResponseBasedOnFile(file_path
).Pass();
147 // Check for query params to see which dictionary to advertise.
148 // For instance, ?q=dictionaryA will make the server advertise dictionaryA.
149 GURL url
= g_test_server
->GetURL(request
.relative_url
);
150 std::string dictionary
;
151 if (!net::GetValueForKeyInQuery(url
, "q", &dictionary
)) {
152 CHECK(false) << "dictionary is not found in query params of "
153 << request
.relative_url
;
155 auto accept_encoding_header
= request
.headers
.find("Accept-Encoding");
156 if (accept_encoding_header
!= request
.headers
.end()) {
157 if (accept_encoding_header
->second
.find("sdch") != std::string::npos
)
158 response
->AddHeader(base::StringPrintf(
159 "Get-Dictionary: %s%s", kSdchDictPath
, dictionary
.c_str()));
161 return response
.Pass();
164 if (StartsWithASCII(request
.relative_url
, kSdchTestPath
, true)) {
165 auto avail_dictionary_header
= request
.headers
.find("Avail-Dictionary");
166 if (avail_dictionary_header
!= request
.headers
.end()) {
167 base::FilePath file_path
= dir_path
.Append(
168 "sdch/" + avail_dictionary_header
->second
+ "_encoded");
169 return ConstructResponseBasedOnFile(file_path
).Pass();
171 scoped_ptr
<net::test_server::BasicHttpResponse
> response(
172 new net::test_server::BasicHttpResponse());
173 response
->set_content_type("text/plain");
174 response
->set_content("Sdch is not used.\n");
175 return response
.Pass();
178 // Unhandled requests result in the Embedded test server sending a 404.
179 return scoped_ptr
<net::test_server::BasicHttpResponse
>();
182 void RegisterHostResolverProcHelper(
183 net::URLRequestContext
* url_request_context
) {
184 net::HostResolverImpl
* resolver
=
185 static_cast<net::HostResolverImpl
*>(url_request_context
->host_resolver());
186 scoped_refptr
<net::RuleBasedHostResolverProc
> proc
=
187 new net::RuleBasedHostResolverProc(NULL
);
188 proc
->AddRule(kFakeSdchDomain
, "127.0.0.1");
189 resolver
->set_proc_params_for_test(
190 net::HostResolverImpl::ProcTaskParams(proc
.get(), 1u));
191 JNIEnv
* env
= base::android::AttachCurrentThread();
192 Java_NativeTestServer_onHostResolverProcRegistered(env
);
195 void RegisterHostResolverProcOnNetworkThread(
196 CronetURLRequestContextAdapter
* context_adapter
) {
197 RegisterHostResolverProcHelper(context_adapter
->GetURLRequestContext());
200 // TODO(xunjieli): Delete this once legacy API is removed.
201 void RegisterHostResolverProcOnNetworkThreadLegacyAPI(
202 URLRequestContextAdapter
* context_adapter
) {
203 RegisterHostResolverProcHelper(context_adapter
->GetURLRequestContext());
208 jboolean
StartNativeTestServer(JNIEnv
* env
,
210 jstring jtest_files_root
) {
214 g_test_server
= new net::test_server::EmbeddedTestServer();
215 g_test_server
->RegisterRequestHandler(
216 base::Bind(&NativeTestServerRequestHandler
));
217 g_test_server
->RegisterRequestHandler(base::Bind(&SdchRequestHandler
));
218 base::FilePath
test_files_root(
219 base::android::ConvertJavaStringToUTF8(env
, jtest_files_root
));
221 // Add a third handler for paths that NativeTestServerRequestHandler does not
223 g_test_server
->ServeFilesFromDirectory(test_files_root
);
224 return g_test_server
->InitializeAndWaitUntilReady();
227 void RegisterHostResolverProc(JNIEnv
* env
,
230 jboolean jlegacy_api
) {
231 if (jlegacy_api
== JNI_TRUE
) {
232 URLRequestContextAdapter
* context_adapter
=
233 reinterpret_cast<URLRequestContextAdapter
*>(jadapter
);
234 context_adapter
->PostTaskToNetworkThread(
235 FROM_HERE
, base::Bind(&RegisterHostResolverProcOnNetworkThreadLegacyAPI
,
236 base::Unretained(context_adapter
)));
238 CronetURLRequestContextAdapter
* context_adapter
=
239 reinterpret_cast<CronetURLRequestContextAdapter
*>(jadapter
);
240 context_adapter
->PostTaskToNetworkThread(
241 FROM_HERE
, base::Bind(&RegisterHostResolverProcOnNetworkThread
,
242 base::Unretained(context_adapter
)));
246 void ShutdownNativeTestServer(JNIEnv
* env
, jclass jcaller
) {
249 delete g_test_server
;
250 g_test_server
= NULL
;
253 jstring
GetEchoBodyURL(JNIEnv
* env
, jclass jcaller
) {
254 DCHECK(g_test_server
);
255 GURL url
= g_test_server
->GetURL(kEchoBodyPath
);
256 return base::android::ConvertUTF8ToJavaString(env
, url
.spec()).Release();
259 jstring
GetEchoHeaderURL(JNIEnv
* env
, jclass jcaller
, jstring jheader
) {
260 DCHECK(g_test_server
);
261 GURL url
= g_test_server
->GetURL(kEchoHeaderPath
);
262 GURL::Replacements replacements
;
263 std::string header
= base::android::ConvertJavaStringToUTF8(env
, jheader
);
264 replacements
.SetQueryStr(header
.c_str());
265 url
= url
.ReplaceComponents(replacements
);
266 return base::android::ConvertUTF8ToJavaString(env
, url
.spec()).Release();
269 jstring
GetEchoAllHeadersURL(JNIEnv
* env
, jclass jcaller
) {
270 DCHECK(g_test_server
);
271 GURL url
= g_test_server
->GetURL(kEchoAllHeadersPath
);
272 return base::android::ConvertUTF8ToJavaString(env
, url
.spec()).Release();
275 jstring
GetEchoMethodURL(JNIEnv
* env
, jclass jcaller
) {
276 DCHECK(g_test_server
);
277 GURL url
= g_test_server
->GetURL(kEchoMethodPath
);
278 return base::android::ConvertUTF8ToJavaString(env
, url
.spec()).Release();
281 jstring
GetRedirectToEchoBody(JNIEnv
* env
, jclass jcaller
) {
282 DCHECK(g_test_server
);
283 GURL url
= g_test_server
->GetURL(kRedirectToEchoBodyPath
);
284 return base::android::ConvertUTF8ToJavaString(env
, url
.spec()).Release();
287 jstring
GetFileURL(JNIEnv
* env
, jclass jcaller
, jstring jfile_path
) {
288 DCHECK(g_test_server
);
289 std::string file
= base::android::ConvertJavaStringToUTF8(env
, jfile_path
);
290 GURL url
= g_test_server
->GetURL(file
);
291 return base::android::ConvertUTF8ToJavaString(env
, url
.spec()).Release();
294 jstring
GetSdchURL(JNIEnv
* env
, jclass jcaller
) {
295 DCHECK(g_test_server
);
296 std::string
url(base::StringPrintf("http://%s:%d", kFakeSdchDomain
,
297 g_test_server
->port()));
298 return base::android::ConvertUTF8ToJavaString(env
, url
).Release();
301 bool RegisterNativeTestServer(JNIEnv
* env
) {
302 return RegisterNativesImpl(env
);
305 } // namespace cronet