package com.vtence.molecule.testing.http;
import com.vtence.molecule.helpers.Headers;
import com.vtence.molecule.helpers.Joiner;
import com.vtence.molecule.helpers.Streams;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static com.vtence.molecule.ssl.SecureProtocol.TLS;
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.toList;
public class HttpRequest {
private final String host;
private final int port;
private final Headers headers = new Headers();
private final Map<String, String> cookies = new LinkedHashMap<>();
private String path = "/";
private String method = "GET";
private HttpContent content = new EmptyContent();
private boolean followRedirects;
private boolean secure;
public HttpRequest(int port) {
this("localhost", port);
}
public HttpRequest(String host, int port) {
this.host = host;
this.port = port;
}
public HttpRequest path(String path) {
this.path = path;
return this;
}
public HttpRequest header(String name, String... values) {
return header(name, asList(values));
}
public HttpRequest header(String name, Iterable<String> values) {
headers.remove(name);
for (String value : values) {
headers.add(name, value);
}
return this;
}
public HttpRequest contentType(String contentType) {
return header("Content-Type", contentType);
}
public HttpRequest cookie(String name, String value) {
cookies.put(name, value);
return this;
}
private String contentType() {
return headers.get("Content-Type");
}
public HttpRequest body(String text) {
this.content = new TextContent(text, contentType());
return body(text.getBytes());
}
public HttpRequest body(byte[] content) {
this.content = new BinaryContent(content, contentType());
return this;
}
public HttpRequest content(HttpContent content) {
this.content = content;
contentType(content.contentType());
return this;
}
public HttpRequest method(String method) {
this.method = method;
return this;
}
public HttpRequest followRedirects(boolean follow) {
this.followRedirects = follow;
return this;
}
public HttpRequest secure(boolean secure) {
this.secure = secure;
return this;
}
public HttpResponse get(String path) throws IOException {
path(path);
method("GET");
return send();
}
public HttpResponse post(String path) throws IOException {
path(path);
method("POST");
return send();
}
public HttpResponse put(String path) throws IOException {
path(path);
method("PUT");
return send();
}
public HttpResponse delete(String path) throws IOException {
path(path);
method("DELETE");
return send();
}
public HttpRequest but() {
HttpRequest request = new HttpRequest(host, port);
request.path(path);
request.method(method);
request.followRedirects(followRedirects);
request.secure(secure);
request.content(content);
for (String header : headers.names()) {
request.header(header, headers.list(header));
}
for (String cookie : cookies.keySet()) {
request.cookie(cookie, cookies.get(cookie));
}
return request;
}
public HttpResponse send() throws IOException {
HttpURLConnection connection = secure ? openSecureConnection() : openConnection();
connection.setRequestMethod(method);
addCookieHeader();
setRequestHeaders(connection);
writeContent(connection);
connection.setInstanceFollowRedirects(followRedirects);
connection.connect();
int statusCode = connection.getResponseCode();
String statusMessage = connection.getResponseMessage();
Map<String, List<String>> headers = connection.getHeaderFields();
byte[] body = readResponseBody(connection);
connection.disconnect();
return new HttpResponse(statusCode, statusMessage, headers, body);
}
private HttpURLConnection openConnection() throws IOException {
return (HttpURLConnection) new URL("http", host, port, path).openConnection();
}
private HttpURLConnection openSecureConnection() throws IOException {
HttpsURLConnection connection = (HttpsURLConnection) new URL("https", host, port, path).openConnection();
SSLContext sslContext;
try {
sslContext = TLS.initialize(null, new TrustManager[] { Trust.allCertificates() });
} catch (GeneralSecurityException e) {
throw new AssertionError("Failed to setup SSL", e);
}
connection.setSSLSocketFactory(sslContext.getSocketFactory());
connection.setHostnameVerifier(Trust.allHostNames());
return connection;
}
private byte[] readResponseBody(HttpURLConnection connection) throws IOException {
InputStream bodyStream = successful(connection) ? connection.getInputStream() : connection.getErrorStream();
return bodyStream != null ? Streams.consume(bodyStream) : new byte[0];
}
private boolean successful(HttpURLConnection connection) throws IOException {
return connection.getResponseCode() < 400;
}
private void setRequestHeaders(HttpURLConnection connection) {
for (String name : headers.names()) {
for (String value: headers.list(name)) {
connection.addRequestProperty(name, value);
}
}
}
private void addCookieHeader() {
if (cookies.isEmpty()) return;
List<String> pairs = cookies.keySet().stream().map(name -> name + "=" + cookies.get(name)).collect(toList());
header("Cookie", Joiner.on("; ").join(pairs));
}
private void writeContent(HttpURLConnection connection) throws IOException {
if (content.contentLength() > 0) {
connection.setDoOutput(true);
content.writeTo(connection.getOutputStream());
}
}
}