package de.otto.hmac.proxy;
import com.google.common.io.ByteSource;
import com.google.common.io.ByteStreams;
import com.google.common.io.FileBackedOutputStream;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import de.otto.hmac.authentication.jersey.HMACJerseyClient;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@Path("/")
public class ProxyResource {
@Path("{resource:.*}")
@GET
public Response getRequest(@Context UriInfo uriInfo, @Context Request request, @Context HttpHeaders headers) {
ClientResponse clientResponse = createBuilder(uriInfo, request.getMethod(), headers).get(ClientResponse.class);
return clientResponseToResponse(clientResponse);
}
@Path("{resource:.*}")
@POST
public Response postRequest(InputStream body, @Context UriInfo uriInfo, @Context Request request, @Context HttpHeaders headers) {
FileBackedOutputStream bodySource = null;
try {
bodySource = toFileBackedOutputStream(body);
ByteSource bodyAsByteSource = bodySource.asByteSource();
ClientResponse clientResponse = createBuilder(uriInfo, bodyAsByteSource, request.getMethod(), headers).post(ClientResponse.class, toStreamingOutput(bodyAsByteSource));
return clientResponseToResponse(clientResponse);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (bodySource != null) {
try {
bodySource.close();
} catch (IOException ignore) {
//
}
}
}
}
protected static StreamingOutput toStreamingOutput(final InputStream inputStream) {
return new StreamingOutput() {
@Override
public void write(OutputStream output) throws IOException, WebApplicationException {
ByteStreams.copy(inputStream, output);
}
};
}
protected static StreamingOutput toStreamingOutput(final ByteSource byteSource) {
return new StreamingOutput() {
@Override
public void write(OutputStream output) throws IOException, WebApplicationException {
byteSource.copyTo(output);
}
};
}
@Path("{resource:.*}")
@PUT
public Response putRequest(InputStream body, @Context UriInfo uriInfo, @Context Request request, @Context HttpHeaders headers) {
FileBackedOutputStream bodySource = null;
try {
bodySource = toFileBackedOutputStream(body);
ByteSource bodyAsByteSource = bodySource.asByteSource();
ClientResponse clientResponse = createBuilder(uriInfo, bodyAsByteSource, request.getMethod(), headers).put(ClientResponse.class, toStreamingOutput(bodyAsByteSource));
return clientResponseToResponse(clientResponse);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (bodySource != null) {
try {
bodySource.close();
} catch (IOException ignore) {
//
}
}
}
}
@Path("{resource:.*}")
@DELETE
public Response deleteRequest(@Context UriInfo uriInfo, @Context Request request, @Context HttpHeaders headers) {
ClientResponse clientResponse = createBuilder(uriInfo, request.getMethod(), headers, HttpHeaders.CONTENT_LENGTH).delete(ClientResponse.class);
return clientResponseToResponse(clientResponse);
}
@Path("{resource:.*}")
@HEAD
public Response headRequest(@Context UriInfo uriInfo, @Context Request request, @Context HttpHeaders headers) {
ClientResponse clientResponse = createBuilder(uriInfo, request.getMethod(), headers).head();
return clientResponseToResponse(clientResponse);
}
@Path("{resource:.*}")
@OPTIONS
public Response optionsRequest(@Context UriInfo uriInfo, @Context Request request, @Context HttpHeaders headers) {
ClientResponse clientResponse = createBuilder(uriInfo, request.getMethod(), headers).options(ClientResponse.class);
return clientResponseToResponse(clientResponse);
}
private static Response clientResponseToResponse(ClientResponse clientResponse) {
Response.ResponseBuilder rb = Response.status(clientResponse.getStatus());
copyResponseHeaders(clientResponse, rb, ignoreResponseHeaders);
FileBackedOutputStream bodySource = null;
try {
bodySource = toFileBackedOutputStream(clientResponse.getEntityInputStream());
ByteSource bodyAsByteSource = bodySource.asByteSource();
System.out.println(String.format("Retrieved answer: HTTP-Code [%d], Content-Length: %s\n\n", clientResponse.getStatus(), bodyAsByteSource.size()));
rb.entity(toStreamingOutput(bodyAsByteSource));
return rb.build();
} catch(IOException e) {
throw new RuntimeException(e);
} finally {
if(bodySource != null) {
try {
bodySource.close();
} catch (IOException ignore) {
//
}
}
}
}
private static List<String> ignoreResponseHeaders = Arrays.asList("Transfer-Encoding".toLowerCase(), HttpHeaders.CONTENT_LENGTH.toLowerCase());
private static Response clientResponseToResponseDirect(ClientResponse clientResponse) {
// nicht schneller, man sieht am client nur eher, was passiert, clientResponseToResponse finde ich schöner :)
Response.ResponseBuilder rb = Response.status(clientResponse.getStatus());
copyResponseHeaders(clientResponse, rb, ignoreResponseHeaders);
try {
System.out.println(String.format("Retrieved answer: HTTP-Code [%d]\n", clientResponse.getStatus()));
rb.entity(toStreamingOutput(clientResponse.getEntityInputStream()));
return rb.build();
} finally {
}
}
private static void copyResponseHeaders(ClientResponse r, Response.ResponseBuilder rb, List<String> ignoreHeaders) {
for (Map.Entry<String, List<String>> entry : r.getHeaders().entrySet()) {
for (String value : entry.getValue()) {
if (ignoreHeaders.contains(entry.getKey().toLowerCase())) {
continue;
}
rb.header(entry.getKey(), value);
}
}
}
private static void copyRequestHeaders(HttpHeaders headers, WebResource.Builder builder, List<String> ignoreHeaders) {
if (headers == null) {
return;
}
for (Map.Entry<String, List<String>> entry : headers.getRequestHeaders().entrySet()) {
if (ignoreHeaders.contains(entry.getKey().toLowerCase())) {
continue;
}
for (String value : entry.getValue()) {
builder.header(entry.getKey(), value);
}
}
}
private WebResource.Builder createBuilder(UriInfo uriInfo, ByteSource body, String method, HttpHeaders headers, String... ignoreHeaders) {
URI targetUri = withTargetHostAndPort(uriInfo.getRequestUriBuilder());
System.out.println("Sending request to " + targetUri);
WebResource.Builder builder = webResourceWithAuth(body, method, targetUri);
ArrayList<String> allIgnoreHeaders = of(ignoreHeaders);
allIgnoreHeaders.add(HttpHeaders.ACCEPT_ENCODING.toLowerCase());
allIgnoreHeaders.add(HttpHeaders.CONTENT_LENGTH.toLowerCase());
allIgnoreHeaders.add(HttpHeaders.HOST.toLowerCase());
copyRequestHeaders(headers, builder, allIgnoreHeaders);
return addHostRequestHeader(builder);
}
private WebResource.Builder addHostRequestHeader(WebResource.Builder builder) {
return builder.header(HttpHeaders.HOST, ProxyConfiguration.getTargetHost());
}
private static ArrayList<String> of(String[] ignoreHeaders) {
ArrayList<String> allIgnoreHeaders = new ArrayList<String>();
for (String ignoreHeader : ignoreHeaders) {
allIgnoreHeaders.add(ignoreHeader.toLowerCase());
}
return allIgnoreHeaders;
}
protected WebResource.Builder createBuilder(UriInfo uriInfo, String method, HttpHeaders headers, String... ignoreHeaders) {
return createBuilder(uriInfo, ByteSource.wrap("".getBytes()), method, headers, ignoreHeaders);
}
protected static FileBackedOutputStream toFileBackedOutputStream(InputStream in) throws IOException {
FileBackedOutputStream out = new FileBackedOutputStream(10*1000*1000, true);
try {
ByteStreams.copy(in, out);
} catch(IOException e) {
out.close();
throw e;
}
return out;
}
protected WebResource.Builder webResourceWithAuth(ByteSource body, String method, URI targetUri) {
try {
WebResource.Builder builder = HMACJerseyClient
.create()
.withMethod(method)
.withUri(targetUri.getPath())
.withBody(body)
.auth(ProxyConfiguration.getUser(), ProxyConfiguration.getPassword())
.authenticatedResource(targetUri.toString());
return builder;
} catch(IOException e) {
throw new WebApplicationException(e);
}
}
protected URI withTargetHostAndPort(UriBuilder uriBuilder) {
uriBuilder.host(ProxyConfiguration.getTargetHost());
uriBuilder.port(ProxyConfiguration.getPort());
return uriBuilder.build();
}
}