зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1405081 - Add partial HttpBin implementation for local HTTP tests r=esawin
This was derived from work done by Andrew Gaul <andrew@gaul.org> for the Chaos HTTP proxy. Differential Revision: https://phabricator.services.mozilla.com/D9130
This commit is contained in:
Родитель
a464ab21e5
Коммит
3b0bbc9bfa
|
@ -226,6 +226,8 @@ dependencies {
|
|||
androidTestImplementation 'com.android.support.test:rules:1.0.1'
|
||||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
|
||||
androidTestImplementation "com.android.support:support-annotations:$support_library_version"
|
||||
|
||||
androidTestImplementation 'org.eclipse.jetty:jetty-server:7.6.21.v20160908'
|
||||
}
|
||||
|
||||
apply from: "${topsrcdir}/mobile/android/gradle/with_gecko_binaries.gradle"
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* Copyright 2015-2016 Bounce Storage, Inc. <info@bouncestorage.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.mozilla.geckoview.test.util;
|
||||
|
||||
import android.os.StrictMode;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.eclipse.jetty.server.Server;
|
||||
|
||||
import org.eclipse.jetty.util.log.AbstractLogger;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.thread.ExecutorThreadPool;
|
||||
|
||||
/**
|
||||
* Reimplementation of HttpBin http://httpbin.org/ suitable for offline unit
|
||||
* tests.
|
||||
*/
|
||||
public final class HttpBin {
|
||||
private static final String LOGTAG = "HttpBin";
|
||||
private final Server mServer;
|
||||
|
||||
static {
|
||||
org.eclipse.jetty.util.log.Log.setLog(new AndroidLogger());
|
||||
}
|
||||
|
||||
public HttpBin(@NonNull URI endpoint) {
|
||||
this(endpoint, new HttpBinHandler());
|
||||
}
|
||||
|
||||
public HttpBin(@NonNull URI endpoint, @NonNull HttpBinHandler handler) {
|
||||
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder(
|
||||
StrictMode.getThreadPolicy())
|
||||
.permitNetwork()
|
||||
.build());
|
||||
|
||||
mServer = new Server(endpoint.getPort());
|
||||
mServer.setHandler(handler);
|
||||
}
|
||||
|
||||
public void start() throws Exception {
|
||||
mServer.start();
|
||||
}
|
||||
|
||||
public void stop() throws Exception {
|
||||
mServer.stop();
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return mServer.getConnectors()[0].getLocalPort();
|
||||
}
|
||||
|
||||
// We need this because the default Logger tries to use some things
|
||||
// that don't exist in older versions of Android
|
||||
private static class AndroidLogger extends AbstractLogger {
|
||||
private boolean mDebugEnabled;
|
||||
|
||||
@Override
|
||||
protected Logger newLogger(String fullname) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String msg, Object... args) {
|
||||
Log.w(LOGTAG, String.format(msg, args));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(Throwable thrown) {
|
||||
Log.w(LOGTAG, thrown);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String msg, Throwable thrown) {
|
||||
Log.w(LOGTAG, msg, thrown);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String msg, Object... args) {
|
||||
Log.i(LOGTAG, String.format(msg, args));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(Throwable thrown) {
|
||||
Log.i(LOGTAG, thrown.getMessage(), thrown);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String msg, Throwable thrown) {
|
||||
Log.i(LOGTAG, msg, thrown);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDebugEnabled() {
|
||||
return mDebugEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDebugEnabled(boolean enabled) {
|
||||
mDebugEnabled = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String msg, Object... args) {
|
||||
if (mDebugEnabled) {
|
||||
Log.d(LOGTAG, String.format(msg, args));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(Throwable thrown) {
|
||||
if (mDebugEnabled) {
|
||||
Log.d(LOGTAG, thrown.getMessage(), thrown);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String msg, Throwable thrown) {
|
||||
if (mDebugEnabled) {
|
||||
Log.d(LOGTAG, msg, thrown);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ignore(Throwable ignored) {
|
||||
// This is pretty spammy
|
||||
if (mDebugEnabled) {
|
||||
Log.w(LOGTAG, "Ignored Exception: ", ignored);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
/*
|
||||
* Copyright 2015-2016 Bounce Storage, Inc. <info@bouncestorage.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.mozilla.geckoview.test.util;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import java.util.Enumeration;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
class HttpBinHandler extends AbstractHandler {
|
||||
private static final String LOGTAG = "HttpBinHandler";
|
||||
private static final int BUFSIZE = 4096;
|
||||
|
||||
private static void pipe(final @NonNull InputStream is) throws IOException {
|
||||
pipe(is, null);
|
||||
}
|
||||
|
||||
private static void pipe(final @NonNull InputStream is, final @Nullable OutputStream os)
|
||||
throws IOException {
|
||||
final byte[] buf = new byte[BUFSIZE];
|
||||
int count = 0;
|
||||
while ((count = is.read(buf)) > 0) {
|
||||
if (os != null) {
|
||||
os.write(buf, 0, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void respondJSON(HttpServletResponse response, OutputStream os, JSONObject obj)
|
||||
throws IOException {
|
||||
final byte[] body = obj.toString().getBytes();
|
||||
|
||||
response.setContentLength(body.length);
|
||||
response.setContentType("application/json");
|
||||
response.setStatus(HttpServletResponse.SC_OK);
|
||||
os.write(body);
|
||||
os.flush();
|
||||
}
|
||||
|
||||
private void redirectTo(HttpServletResponse response, String location) {
|
||||
response.setHeader("Location", location);
|
||||
response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void handle(String target, Request baseRequest,
|
||||
HttpServletRequest request, HttpServletResponse servletResponse)
|
||||
throws IOException {
|
||||
String method = request.getMethod();
|
||||
String uri = request.getRequestURI();
|
||||
try (InputStream is = request.getInputStream();
|
||||
OutputStream os = servletResponse.getOutputStream()) {
|
||||
if (method.equals("GET") && uri.startsWith("/status/")) {
|
||||
pipe(is);
|
||||
int status = Integer.parseInt(uri.substring(
|
||||
"/status/".length()));
|
||||
servletResponse.setStatus(status);
|
||||
baseRequest.setHandled(true);
|
||||
} else if (uri.equals("/redirect-to")) {
|
||||
pipe(is);
|
||||
redirectTo(servletResponse, request.getParameter("url"));
|
||||
baseRequest.setHandled(true);
|
||||
} else if (uri.startsWith("/redirect/")) {
|
||||
pipe(is);
|
||||
|
||||
int count = Integer.parseInt(uri.substring("/redirect/".length())) - 1;
|
||||
if (count > 0) {
|
||||
redirectTo(servletResponse, "/redirect/" + count);
|
||||
} else {
|
||||
servletResponse.setStatus(HttpServletResponse.SC_OK);
|
||||
}
|
||||
|
||||
baseRequest.setHandled(true);
|
||||
} else if (uri.equals("/cookies")) {
|
||||
pipe(is);
|
||||
|
||||
final JSONObject cookies = new JSONObject();
|
||||
|
||||
if (request.getCookies() != null) {
|
||||
for (final Cookie cookie : request.getCookies()) {
|
||||
cookies.put(cookie.getName(), cookie.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
final JSONObject response = new JSONObject();
|
||||
response.put("cookies", cookies);
|
||||
|
||||
respondJSON(servletResponse, os, response);
|
||||
baseRequest.setHandled(true);
|
||||
} else if (uri.startsWith("/cookies/set/")) {
|
||||
pipe(is);
|
||||
|
||||
final String[] parts = uri.substring("/cookies/set/".length()).split("/");
|
||||
|
||||
servletResponse.addHeader("Set-Cookie",
|
||||
String.format("%s=%s; Path=/", parts[0], parts[1]));
|
||||
|
||||
servletResponse.setHeader("Location", "/cookies");
|
||||
servletResponse.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
|
||||
baseRequest.setHandled(true);
|
||||
} else if (uri.startsWith("/basic-auth")) {
|
||||
pipe(is);
|
||||
|
||||
// FIXME: we don't actually check the username/password here
|
||||
servletResponse.addHeader("WWW-Authenticate",
|
||||
"Basic realm=\"Fake Realm\"");
|
||||
servletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
baseRequest.setHandled(true);
|
||||
} else if (uri.equals("/anything")) {
|
||||
servletResponse.setStatus(HttpServletResponse.SC_OK);
|
||||
baseRequest.setHandled(true);
|
||||
|
||||
final JSONObject response = new JSONObject();
|
||||
|
||||
// Method
|
||||
response.put("method", method);
|
||||
|
||||
// Headers
|
||||
final JSONObject headers = new JSONObject();
|
||||
response.put("headers", headers);
|
||||
|
||||
for (Enumeration<String> names = request.getHeaderNames(); names.hasMoreElements();) {
|
||||
final String name = names.nextElement();
|
||||
headers.put(name, request.getHeader(name));
|
||||
}
|
||||
|
||||
// Body data
|
||||
final ByteArrayOutputStream data = new ByteArrayOutputStream();
|
||||
pipe(is, data);
|
||||
|
||||
response.put("data", data.toString("UTF-8"));
|
||||
respondJSON(servletResponse, os, response);
|
||||
baseRequest.setHandled(true);
|
||||
}
|
||||
|
||||
if (!baseRequest.isHandled()) {
|
||||
servletResponse.setStatus(501);
|
||||
baseRequest.setHandled(true);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "JSON error while handling response", e);
|
||||
servletResponse.setStatus(500);
|
||||
baseRequest.setHandled(true);
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче