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/host_port_pair.h"
24 #include "net/base/url_util.h"
25 #include "net/dns/host_resolver_impl.h"
26 #include "net/dns/mock_host_resolver.h"
27 #include "net/http/http_status_code.h"
28 #include "net/test/embedded_test_server/embedded_test_server.h"
29 #include "net/test/embedded_test_server/http_request.h"
30 #include "net/test/embedded_test_server/http_response.h"
37 const char kEchoBodyPath
[] = "/echo_body";
38 const char kEchoHeaderPath
[] = "/echo_header";
39 const char kEchoAllHeadersPath
[] = "/echo_all_headers";
40 const char kEchoMethodPath
[] = "/echo_method";
41 const char kRedirectToEchoBodyPath
[] = "/redirect_to_echo_body";
42 const char kFakeSdchDomain
[] = "fake.sdch.domain";
43 // Path that advertises the dictionary passed in query params if client
44 // supports Sdch encoding. E.g. /sdch/index?q=LeQxM80O will make the server
45 // responds with "Get-Dictionary: /sdch/dict/LeQxM80O".
46 const char kSdchPath
[] = "/sdch/index";
47 // Path that returns encoded response if client has the right dictionary.
48 const char kSdchTestPath
[] = "/sdch/test";
49 // Path where dictionaries are stored.
50 const char kSdchDictPath
[] = "/sdch/dict/";
52 net::test_server::EmbeddedTestServer
* g_test_server
= nullptr;
54 class CustomHttpResponse
: public net::test_server::HttpResponse
{
56 CustomHttpResponse(const std::string
& headers
, const std::string
& contents
)
57 : headers_(headers
), contents_(contents
) {}
59 std::string
ToResponseString() const override
{
60 return headers_
+ "\r\n" + contents_
;
63 void AddHeader(const std::string
& key_value_pair
) {
64 headers_
.append(base::StringPrintf("%s\r\n", key_value_pair
.c_str()));
69 std::string contents_
;
71 DISALLOW_COPY_AND_ASSIGN(CustomHttpResponse
);
74 scoped_ptr
<CustomHttpResponse
> ConstructResponseBasedOnFile(
75 const base::FilePath
& file_path
) {
76 std::string file_contents
;
77 bool read_file
= base::ReadFileToString(file_path
, &file_contents
);
79 base::FilePath
headers_path(
80 file_path
.AddExtension(FILE_PATH_LITERAL("mock-http-headers")));
81 std::string headers_contents
;
82 bool read_headers
= base::ReadFileToString(headers_path
, &headers_contents
);
84 scoped_ptr
<CustomHttpResponse
> http_response(
85 new CustomHttpResponse(headers_contents
, file_contents
));
86 return http_response
.Pass();
89 scoped_ptr
<net::test_server::HttpResponse
> NativeTestServerRequestHandler(
90 const net::test_server::HttpRequest
& request
) {
91 DCHECK(g_test_server
);
92 scoped_ptr
<net::test_server::BasicHttpResponse
> response(
93 new net::test_server::BasicHttpResponse());
94 response
->set_content_type("text/plain");
96 if (request
.relative_url
== kEchoBodyPath
) {
97 if (request
.has_content
) {
98 response
->set_content(request
.content
);
100 response
->set_content("Request has no body. :(");
102 return response
.Pass();
105 if (base::StartsWith(request
.relative_url
, kEchoHeaderPath
,
106 base::CompareCase::SENSITIVE
)) {
107 GURL url
= g_test_server
->GetURL(request
.relative_url
);
108 auto it
= request
.headers
.find(url
.query());
109 if (it
!= request
.headers
.end()) {
110 response
->set_content(it
->second
);
112 response
->set_content("Header not found. :(");
114 return response
.Pass();
117 if (request
.relative_url
== kEchoAllHeadersPath
) {
118 response
->set_content(request
.all_headers
);
119 return response
.Pass();
122 if (request
.relative_url
== kEchoMethodPath
) {
123 response
->set_content(request
.method_string
);
124 return response
.Pass();
127 if (request
.relative_url
== kRedirectToEchoBodyPath
) {
128 response
->set_code(net::HTTP_TEMPORARY_REDIRECT
);
129 response
->AddCustomHeader("Location", kEchoBodyPath
);
130 return response
.Pass();
133 // Unhandled requests result in the Embedded test server sending a 404.
134 return scoped_ptr
<net::test_server::BasicHttpResponse
>();
137 scoped_ptr
<net::test_server::HttpResponse
> SdchRequestHandler(
138 const net::test_server::HttpRequest
& request
) {
139 DCHECK(g_test_server
);
140 base::FilePath dir_path
;
141 bool get_data_dir
= base::android::GetDataDirectory(&dir_path
);
142 DCHECK(get_data_dir
);
143 dir_path
= dir_path
.Append(FILE_PATH_LITERAL("test"));
145 if (base::StartsWith(request
.relative_url
, kSdchPath
,
146 base::CompareCase::SENSITIVE
)) {
147 base::FilePath file_path
= dir_path
.Append("sdch/index");
148 scoped_ptr
<CustomHttpResponse
> response
=
149 ConstructResponseBasedOnFile(file_path
).Pass();
150 // Check for query params to see which dictionary to advertise.
151 // For instance, ?q=dictionaryA will make the server advertise dictionaryA.
152 GURL url
= g_test_server
->GetURL(request
.relative_url
);
153 std::string dictionary
;
154 if (!net::GetValueForKeyInQuery(url
, "q", &dictionary
)) {
155 CHECK(false) << "dictionary is not found in query params of "
156 << request
.relative_url
;
158 auto accept_encoding_header
= request
.headers
.find("Accept-Encoding");
159 if (accept_encoding_header
!= request
.headers
.end()) {
160 if (accept_encoding_header
->second
.find("sdch") != std::string::npos
)
161 response
->AddHeader(base::StringPrintf(
162 "Get-Dictionary: %s%s", kSdchDictPath
, dictionary
.c_str()));
164 return response
.Pass();
167 if (base::StartsWith(request
.relative_url
, kSdchTestPath
,
168 base::CompareCase::SENSITIVE
)) {
169 auto avail_dictionary_header
= request
.headers
.find("Avail-Dictionary");
170 if (avail_dictionary_header
!= request
.headers
.end()) {
171 base::FilePath file_path
= dir_path
.Append(
172 "sdch/" + avail_dictionary_header
->second
+ "_encoded");
173 return ConstructResponseBasedOnFile(file_path
).Pass();
175 scoped_ptr
<net::test_server::BasicHttpResponse
> response(
176 new net::test_server::BasicHttpResponse());
177 response
->set_content_type("text/plain");
178 response
->set_content("Sdch is not used.\n");
179 return response
.Pass();
182 // Unhandled requests result in the Embedded test server sending a 404.
183 return scoped_ptr
<net::test_server::BasicHttpResponse
>();
186 void RegisterHostResolverProcHelper(
187 net::URLRequestContext
* url_request_context
) {
188 net::HostResolverImpl
* resolver
=
189 static_cast<net::HostResolverImpl
*>(url_request_context
->host_resolver());
190 scoped_refptr
<net::RuleBasedHostResolverProc
> proc
=
191 new net::RuleBasedHostResolverProc(NULL
);
192 proc
->AddRule(kFakeSdchDomain
, "127.0.0.1");
193 resolver
->set_proc_params_for_test(
194 net::HostResolverImpl::ProcTaskParams(proc
.get(), 1u));
195 JNIEnv
* env
= base::android::AttachCurrentThread();
196 Java_NativeTestServer_onHostResolverProcRegistered(env
);
199 void RegisterHostResolverProcOnNetworkThread(
200 CronetURLRequestContextAdapter
* context_adapter
) {
201 RegisterHostResolverProcHelper(context_adapter
->GetURLRequestContext());
204 // TODO(xunjieli): Delete this once legacy API is removed.
205 void RegisterHostResolverProcOnNetworkThreadLegacyAPI(
206 URLRequestContextAdapter
* context_adapter
) {
207 RegisterHostResolverProcHelper(context_adapter
->GetURLRequestContext());
212 jboolean
StartNativeTestServer(JNIEnv
* env
,
213 const JavaParamRef
<jclass
>& jcaller
,
214 const JavaParamRef
<jstring
>& jtest_files_root
) {
218 g_test_server
= new net::test_server::EmbeddedTestServer();
219 g_test_server
->RegisterRequestHandler(
220 base::Bind(&NativeTestServerRequestHandler
));
221 g_test_server
->RegisterRequestHandler(base::Bind(&SdchRequestHandler
));
222 base::FilePath
test_files_root(
223 base::android::ConvertJavaStringToUTF8(env
, jtest_files_root
));
225 // Add a third handler for paths that NativeTestServerRequestHandler does not
227 g_test_server
->ServeFilesFromDirectory(test_files_root
);
228 return g_test_server
->InitializeAndWaitUntilReady();
231 void RegisterHostResolverProc(JNIEnv
* env
,
232 const JavaParamRef
<jclass
>& jcaller
,
234 jboolean jlegacy_api
) {
235 if (jlegacy_api
== JNI_TRUE
) {
236 URLRequestContextAdapter
* context_adapter
=
237 reinterpret_cast<URLRequestContextAdapter
*>(jadapter
);
238 context_adapter
->PostTaskToNetworkThread(
239 FROM_HERE
, base::Bind(&RegisterHostResolverProcOnNetworkThreadLegacyAPI
,
240 base::Unretained(context_adapter
)));
242 CronetURLRequestContextAdapter
* context_adapter
=
243 reinterpret_cast<CronetURLRequestContextAdapter
*>(jadapter
);
244 context_adapter
->PostTaskToNetworkThread(
245 FROM_HERE
, base::Bind(&RegisterHostResolverProcOnNetworkThread
,
246 base::Unretained(context_adapter
)));
250 void ShutdownNativeTestServer(JNIEnv
* env
,
251 const JavaParamRef
<jclass
>& jcaller
) {
254 delete g_test_server
;
255 g_test_server
= NULL
;
258 ScopedJavaLocalRef
<jstring
> GetEchoBodyURL(
260 const JavaParamRef
<jclass
>& jcaller
) {
261 DCHECK(g_test_server
);
262 GURL url
= g_test_server
->GetURL(kEchoBodyPath
);
263 return base::android::ConvertUTF8ToJavaString(env
, url
.spec());
266 ScopedJavaLocalRef
<jstring
> GetEchoHeaderURL(
268 const JavaParamRef
<jclass
>& jcaller
,
269 const JavaParamRef
<jstring
>& jheader
) {
270 DCHECK(g_test_server
);
271 GURL url
= g_test_server
->GetURL(kEchoHeaderPath
);
272 GURL::Replacements replacements
;
273 std::string header
= base::android::ConvertJavaStringToUTF8(env
, jheader
);
274 replacements
.SetQueryStr(header
.c_str());
275 url
= url
.ReplaceComponents(replacements
);
276 return base::android::ConvertUTF8ToJavaString(env
, url
.spec());
279 ScopedJavaLocalRef
<jstring
> GetEchoAllHeadersURL(
281 const JavaParamRef
<jclass
>& jcaller
) {
282 DCHECK(g_test_server
);
283 GURL url
= g_test_server
->GetURL(kEchoAllHeadersPath
);
284 return base::android::ConvertUTF8ToJavaString(env
, url
.spec());
287 ScopedJavaLocalRef
<jstring
> GetEchoMethodURL(
289 const JavaParamRef
<jclass
>& jcaller
) {
290 DCHECK(g_test_server
);
291 GURL url
= g_test_server
->GetURL(kEchoMethodPath
);
292 return base::android::ConvertUTF8ToJavaString(env
, url
.spec());
295 ScopedJavaLocalRef
<jstring
> GetRedirectToEchoBody(
297 const JavaParamRef
<jclass
>& jcaller
) {
298 DCHECK(g_test_server
);
299 GURL url
= g_test_server
->GetURL(kRedirectToEchoBodyPath
);
300 return base::android::ConvertUTF8ToJavaString(env
, url
.spec());
303 ScopedJavaLocalRef
<jstring
> GetFileURL(
305 const JavaParamRef
<jclass
>& jcaller
,
306 const JavaParamRef
<jstring
>& jfile_path
) {
307 DCHECK(g_test_server
);
308 std::string file
= base::android::ConvertJavaStringToUTF8(env
, jfile_path
);
309 GURL url
= g_test_server
->GetURL(file
);
310 return base::android::ConvertUTF8ToJavaString(env
, url
.spec());
313 ScopedJavaLocalRef
<jstring
> GetSdchURL(JNIEnv
* env
,
314 const JavaParamRef
<jclass
>& jcaller
) {
315 DCHECK(g_test_server
);
316 std::string
url(base::StringPrintf("http://%s:%d", kFakeSdchDomain
,
317 g_test_server
->port()));
318 return base::android::ConvertUTF8ToJavaString(env
, url
);
321 ScopedJavaLocalRef
<jstring
> GetHostPort(JNIEnv
* env
,
322 const JavaParamRef
<jclass
>& jcaller
) {
323 DCHECK(g_test_server
);
324 std::string host_port
=
325 net::HostPortPair::FromURL(g_test_server
->base_url()).ToString();
326 return base::android::ConvertUTF8ToJavaString(env
, host_port
);
329 jboolean
IsDataReductionProxySupported(JNIEnv
* env
,
330 const JavaParamRef
<jclass
>& jcaller
) {
331 #if defined(DATA_REDUCTION_PROXY_SUPPORT)
338 bool RegisterNativeTestServer(JNIEnv
* env
) {
339 return RegisterNativesImpl(env
);
342 } // namespace cronet