feat: implement remaining Response methods (#46)

This commit is contained in:
Yury Semikhatsky 2020-10-26 20:10:11 -07:00 committed by GitHub
parent 24b101d071
commit c134a6355f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 177 additions and 21 deletions

View File

@ -243,6 +243,8 @@ class Method extends Element {
"void route(Pattern url, BiConsumer<Route, Request> handler);", "void route(Pattern url, BiConsumer<Route, Request> handler);",
"void route(Predicate<String> url, BiConsumer<Route, Request> handler);", "void route(Predicate<String> url, BiConsumer<Route, Request> handler);",
}); });
// There is no standard JSON type in Java.
customSignature.put("Response.json", new String[0]);
customSignature.put("Page.frame", new String[]{ customSignature.put("Page.frame", new String[]{
"Frame frameByName(String name);", "Frame frameByName(String name);",
"Frame frameByUrl(String glob);", "Frame frameByUrl(String glob);",

View File

@ -273,6 +273,7 @@ class Types {
add("ElementHandle.screenshot", "Promise<Buffer>", "byte[]"); add("ElementHandle.screenshot", "Promise<Buffer>", "byte[]");
add("Request.postDataBuffer", "null|Buffer", "byte[]"); add("Request.postDataBuffer", "null|Buffer", "byte[]");
add("Response.body", "Promise<Buffer>", "byte[]"); add("Response.body", "Promise<Buffer>", "byte[]");
add("Response.finished", "Promise<null|Error>", "String");
add("ChromiumBrowser.stopTracing", "Promise<Buffer>", "byte[]"); add("ChromiumBrowser.stopTracing", "Promise<Buffer>", "byte[]");
// JSON type // JSON type

View File

@ -20,10 +20,9 @@ import java.util.*;
public interface Response { public interface Response {
byte[] body(); byte[] body();
Error finished(); String finished();
Frame frame(); Frame frame();
Map<String, String> headers(); Map<String, String> headers();
Object json();
boolean ok(); boolean ok();
Request request(); Request request();
int status(); int status();

View File

@ -16,20 +16,27 @@
package com.microsoft.playwright.impl; package com.microsoft.playwright.impl;
import com.google.gson.Gson; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.microsoft.playwright.Frame; import com.microsoft.playwright.Frame;
import com.microsoft.playwright.Request; import com.microsoft.playwright.Request;
import com.microsoft.playwright.Response; import com.microsoft.playwright.Response;
import java.nio.charset.StandardCharsets;
import java.util.Base64; import java.util.Base64;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import static com.microsoft.playwright.impl.Serialization.deserialize;
public class ResponseImpl extends ChannelOwner implements Response { public class ResponseImpl extends ChannelOwner implements Response {
private final Map<String, String> headers = new HashMap<>();
ResponseImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { ResponseImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer); super(parent, type, guid, initializer);
for (JsonElement e : initializer.getAsJsonArray("headers")) {
JsonObject item = e.getAsJsonObject();
headers.put(item.get("name").getAsString().toLowerCase(), item.get("value").getAsString());
}
} }
@Override @Override
@ -39,7 +46,11 @@ public class ResponseImpl extends ChannelOwner implements Response {
} }
@Override @Override
public Error finished() { public String finished() {
JsonObject json = sendMessage("finished").getAsJsonObject();
if (json.has("error")) {
return json.get("error").getAsString();
}
return null; return null;
} }
@ -50,12 +61,7 @@ public class ResponseImpl extends ChannelOwner implements Response {
@Override @Override
public Map<String, String> headers() { public Map<String, String> headers() {
return null; return headers;
}
@Override
public Object json() {
return null;
} }
@Override @Override
@ -80,7 +86,7 @@ public class ResponseImpl extends ChannelOwner implements Response {
@Override @Override
public String text() { public String text() {
return null; return new String(body(), StandardCharsets.UTF_8);
} }
@Override @Override

View File

@ -25,6 +25,7 @@ import com.microsoft.playwright.*;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.PrintStream; import java.io.PrintStream;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
@ -38,7 +39,7 @@ class Serialization {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
e.printStackTrace(new PrintStream(out)); e.printStackTrace(new PrintStream(out));
result.error.stack = new String(out.toByteArray()); result.error.stack = new String(out.toByteArray(), StandardCharsets.UTF_8);
return result; return result;
} }

View File

@ -24,8 +24,7 @@ import java.nio.file.FileSystems;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.zip.GZIPOutputStream;
import static java.util.Collections.singletonList;
public class Server implements HttpHandler { public class Server implements HttpHandler {
private final HttpServer server; private final HttpServer server;
@ -40,6 +39,7 @@ public class Server implements HttpHandler {
private final Map<String, Auth> auths = Collections.synchronizedMap(new HashMap<>()); private final Map<String, Auth> auths = Collections.synchronizedMap(new HashMap<>());
private final Map<String, String> csp = Collections.synchronizedMap(new HashMap<>()); private final Map<String, String> csp = Collections.synchronizedMap(new HashMap<>());
private final Map<String, HttpHandler> routes = Collections.synchronizedMap(new HashMap<>()); private final Map<String, HttpHandler> routes = Collections.synchronizedMap(new HashMap<>());
private final Set<String> gzipRoutes = Collections.synchronizedSet(new HashSet<>());
private static class Auth { private static class Auth {
public final String user; public final String user;
@ -93,6 +93,10 @@ public class Server implements HttpHandler {
this.csp.put(path, csp); this.csp.put(path, csp);
} }
void enableGzip(String path) {
gzipRoutes.add(path);
}
static class Request { static class Request {
public final String method; public final String method;
// TODO: make a copy to ensure thread safety? // TODO: make a copy to ensure thread safety?
@ -135,6 +139,7 @@ public class Server implements HttpHandler {
auths.clear(); auths.clear();
csp.clear(); csp.clear();
routes.clear(); routes.clear();
gzipRoutes.clear();
} }
@Override @Override
@ -154,7 +159,7 @@ public class Server implements HttpHandler {
} }
} }
if (!authorized) { if (!authorized) {
exchange.getResponseHeaders().put("WWW-Authenticate", Arrays.asList("Basic realm=\"Secure Area\"")); exchange.getResponseHeaders().add("WWW-Authenticate", "Basic realm=\"Secure Area\"");
exchange.sendResponseHeaders(401, 0); exchange.sendResponseHeaders(401, 0);
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) { try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("HTTP Error 401 Unauthorized: Access is denied"); writer.write("HTTP Error 401 Unauthorized: Access is denied");
@ -180,20 +185,27 @@ public class Server implements HttpHandler {
} }
if (csp.containsKey(path)) { if (csp.containsKey(path)) {
exchange.getResponseHeaders().put("Content-Security-Policy", singletonList(csp.get(path))); exchange.getResponseHeaders().add("Content-Security-Policy", csp.get(path));
} }
File file = new File(resourcesDir, path.substring(1)); File file = new File(resourcesDir, path.substring(1));
exchange.getResponseHeaders().put("Content-Type", singletonList(mimeType(file))); exchange.getResponseHeaders().add("Content-Type", mimeType(file));
OutputStream output = exchange.getResponseBody();
if (gzipRoutes.contains(path)) {
exchange.getResponseHeaders().add("Content-Encoding", "gzip");
}
try (FileInputStream input = new FileInputStream(file)) { try (FileInputStream input = new FileInputStream(file)) {
exchange.sendResponseHeaders(200, 0); exchange.sendResponseHeaders(200, 0);
copy(input, exchange.getResponseBody()); if (gzipRoutes.contains(path)) {
output = new GZIPOutputStream(output);
}
copy(input, output);
} catch (IOException e) { } catch (IOException e) {
exchange.sendResponseHeaders(404, 0); exchange.sendResponseHeaders(404, 0);
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) { try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("File not found: " + file.getCanonicalPath()); writer.write("File not found: " + file.getCanonicalPath());
} }
} }
exchange.getResponseBody().close(); output.close();
} }
private static void copy(InputStream in, OutputStream out) throws IOException { private static void copy(InputStream in, OutputStream out) throws IOException {

View File

@ -0,0 +1,135 @@
/*
* Copyright (c) Microsoft Corporation.
*
* 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 com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.concurrent.Future;
import static com.microsoft.playwright.Page.EventType.REQUESTFINISHED;
import static com.microsoft.playwright.Page.EventType.RESPONSE;
import static org.junit.jupiter.api.Assertions.*;
public class TestNetworkResponse extends TestBase {
@Test
void shouldWork() {
server.setRoute("/empty.html", exchange -> {
exchange.getResponseHeaders().add("foo", "bar");
exchange.getResponseHeaders().add("BaZ", "bAz");
exchange.sendResponseHeaders(200, 0);
exchange.getResponseBody().close();
});
Response response = page.navigate(server.EMPTY_PAGE);
assertEquals("bar", response.headers().get("foo"));
assertEquals("bAz", response.headers().get("baz"));
assertNull(response.headers().get("BaZ"));
}
@Test
void shouldReturnText() {
Response response = page.navigate(server.PREFIX + "/simple.json");
assertEquals("{\"foo\": \"bar\"}\n", response.text());
}
@Test
void shouldReturnUncompressedText() {
server.enableGzip("/simple.json");
Response response = page.navigate(server.PREFIX + "/simple.json");
assertEquals("gzip", response.headers().get("content-encoding"));
assertEquals("{\"foo\": \"bar\"}\n", response.text());
}
@Test
void shouldThrowWhenRequestingBodyOfRedirectedResponse() {
server.setRedirect("/foo.html", "/empty.html");
Response response = page.navigate(server.PREFIX + "/foo.html");
Request redirectedFrom = response.request().redirectedFrom();
assertNotNull(redirectedFrom);
Response redirected = redirectedFrom.response();
assertEquals(302, redirected.status());
try {
redirected.text();
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Response body is unavailable for redirect responses"));
}
}
@Test
void shouldWaitUntilResponseCompletes() {
page.navigate(server.EMPTY_PAGE);
server.setRoute("/get", exchange -> {
// In Firefox, |fetch| will be hanging until it receives |Content-Type| header
// from server.
exchange.getResponseHeaders().add("Content-Type", "text/plain; charset=utf-8");
exchange.sendResponseHeaders(200, 0);
try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("hello ");
writer.flush();
writer.write("wor");
writer.flush();
writer.write("ld!");
}
});
// Setup page to trap response.
boolean[] requestFinished = {false};
page.addListener(REQUESTFINISHED, event -> {
requestFinished[0] |= ((Request) event.data()).url().contains("/get");
});
// send request and wait for server response
Deferred<Event<Page.EventType>> responseEvent = page.waitForEvent(RESPONSE);
Future<Server.Request> request = server.waitForRequest("/get");
page.evaluate("() => fetch('./get', { method: 'GET'})");
assertNotNull(responseEvent.get());
Response pageResponse = (Response) responseEvent.get().data();
assertEquals(200, pageResponse.status());
assertEquals(false, requestFinished[0]);
assertEquals("hello world!", pageResponse.text());
}
void shouldReturnJson() {
// Not exposed in Java.
}
@Test
void shouldReturnBody() throws IOException {
Response response = page.navigate(server.PREFIX + "/pptr.png");
byte[] expected = Files.readAllBytes(new File("src/test/resources/pptr.png").toPath());
assertTrue(Arrays.equals(expected, response.body()));
}
@Test
void shouldReturnBodyWithCompression() throws IOException {
server.enableGzip("/pptr.png");
Response response = page.navigate(server.PREFIX + "/pptr.png");
byte[] expected = Files.readAllBytes(new File("src/test/resources/pptr.png").toPath());
assertTrue(Arrays.equals(expected, response.body()));
}
@Test
void shouldReturnStatusText() {
server.setRoute("/cool", exchange -> {
exchange.sendResponseHeaders(200, 0);
exchange.getResponseBody().close();
});
Response response = page.navigate(server.PREFIX + "/cool");
assertEquals("OK", response.statusText());
}
}