272 lines
7.6 KiB
Java
272 lines
7.6 KiB
Java
/*
|
|
* Copyright (c) 2011-2020 Contributors to the Eclipse Foundation
|
|
*
|
|
* This program and the accompanying materials are made available under the
|
|
* terms of the Eclipse Public License 2.0 which is available at
|
|
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
|
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
|
*
|
|
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
|
*/
|
|
package io.vertx.httpproxy.impl;
|
|
|
|
import io.vertx.core.AsyncResult;
|
|
import io.vertx.core.Future;
|
|
import io.vertx.core.Handler;
|
|
import io.vertx.core.MultiMap;
|
|
import io.vertx.core.Promise;
|
|
import io.vertx.core.buffer.Buffer;
|
|
import io.vertx.core.http.HttpClientResponse;
|
|
import io.vertx.core.http.HttpHeaders;
|
|
import io.vertx.core.http.HttpServerResponse;
|
|
import io.vertx.core.http.HttpVersion;
|
|
import io.vertx.core.streams.Pipe;
|
|
import io.vertx.core.streams.ReadStream;
|
|
import io.vertx.httpproxy.Body;
|
|
import io.vertx.httpproxy.ProxyRequest;
|
|
import io.vertx.httpproxy.ProxyResponse;
|
|
|
|
import java.time.Instant;
|
|
import java.util.ArrayList;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
|
|
class ProxiedResponse implements ProxyResponse {
|
|
|
|
private final ProxiedRequest request;
|
|
private final HttpServerResponse proxiedResponse;
|
|
private int statusCode;
|
|
private String statusMessage;
|
|
private Body body;
|
|
private final MultiMap headers;
|
|
private HttpClientResponse response;
|
|
private long maxAge;
|
|
private String etag;
|
|
private boolean publicCacheControl;
|
|
|
|
ProxiedResponse(ProxiedRequest request, HttpServerResponse proxiedResponse) {
|
|
this.response = null;
|
|
this.statusCode = 200;
|
|
this.headers = MultiMap.caseInsensitiveMultiMap();
|
|
this.request = request;
|
|
this.proxiedResponse = proxiedResponse;
|
|
}
|
|
|
|
ProxiedResponse(ProxiedRequest request, HttpServerResponse proxiedResponse, HttpClientResponse response) {
|
|
|
|
// Determine content length
|
|
long contentLength = -1L;
|
|
String contentLengthHeader = response.getHeader(HttpHeaders.CONTENT_LENGTH);
|
|
if (contentLengthHeader != null) {
|
|
try {
|
|
contentLength = Long.parseLong(contentLengthHeader);
|
|
} catch (NumberFormatException e) {
|
|
// Ignore ???
|
|
}
|
|
}
|
|
|
|
this.request = request;
|
|
this.response = response;
|
|
this.proxiedResponse = proxiedResponse;
|
|
this.statusCode = response.statusCode();
|
|
this.statusMessage = response.statusMessage();
|
|
this.body = Body.body(response, contentLength);
|
|
|
|
long maxAge = -1;
|
|
boolean publicCacheControl = false;
|
|
String cacheControlHeader = response.getHeader(HttpHeaders.CACHE_CONTROL);
|
|
if (cacheControlHeader != null) {
|
|
CacheControl cacheControl = new CacheControl().parse(cacheControlHeader);
|
|
if (cacheControl.isPublic()) {
|
|
publicCacheControl = true;
|
|
if (cacheControl.maxAge() > 0) {
|
|
maxAge = (long)cacheControl.maxAge() * 1000;
|
|
} else {
|
|
String dateHeader = response.getHeader(HttpHeaders.DATE);
|
|
String expiresHeader = response.getHeader(HttpHeaders.EXPIRES);
|
|
if (dateHeader != null && expiresHeader != null) {
|
|
maxAge = ParseUtils.parseHeaderDate(expiresHeader).toEpochMilli() - ParseUtils.parseHeaderDate(dateHeader).toEpochMilli();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
this.maxAge = maxAge;
|
|
this.publicCacheControl = publicCacheControl;
|
|
this.etag = response.getHeader(HttpHeaders.ETAG);
|
|
this.headers = MultiMap.caseInsensitiveMultiMap().addAll(response.headers());
|
|
}
|
|
|
|
@Override
|
|
public ProxyRequest request() {
|
|
return request;
|
|
}
|
|
|
|
@Override
|
|
public int getStatusCode() {
|
|
return statusCode;
|
|
}
|
|
|
|
@Override
|
|
public ProxyResponse setStatusCode(int sc) {
|
|
statusCode = sc;
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public String getStatusMessage() {
|
|
return statusMessage;
|
|
}
|
|
|
|
@Override
|
|
public ProxyResponse setStatusMessage(String statusMessage) {
|
|
this.statusMessage = statusMessage;
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public Body getBody() {
|
|
return body;
|
|
}
|
|
|
|
@Override
|
|
public ProxyResponse setBody(Body body) {
|
|
this.body = body;
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public boolean publicCacheControl() {
|
|
return publicCacheControl;
|
|
}
|
|
|
|
@Override
|
|
public long maxAge() {
|
|
return maxAge;
|
|
}
|
|
|
|
@Override
|
|
public String etag() {
|
|
return etag;
|
|
}
|
|
|
|
@Override
|
|
public MultiMap headers() {
|
|
return headers;
|
|
}
|
|
|
|
@Override
|
|
public ProxyResponse putHeader(CharSequence name, CharSequence value) {
|
|
headers.set(name, value);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public Future<Void> send() {
|
|
Promise<Void> promise = request.context.promise();
|
|
send(promise);
|
|
return promise.future();
|
|
}
|
|
|
|
public void send(Handler<AsyncResult<Void>> completionHandler) {
|
|
// Set stuff
|
|
proxiedResponse.setStatusCode(statusCode);
|
|
|
|
if(statusMessage != null) {
|
|
proxiedResponse.setStatusMessage(statusMessage);
|
|
}
|
|
|
|
// Date header
|
|
Instant date = HttpUtils.dateHeader(headers);
|
|
if (date == null) {
|
|
date = Instant.now();
|
|
}
|
|
try {
|
|
proxiedResponse.putHeader("date", ParseUtils.formatHttpDate(date));
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
|
|
// Warning header
|
|
List<String> warningHeaders = headers.getAll("warning");
|
|
if (warningHeaders.size() > 0) {
|
|
warningHeaders = new ArrayList<>(warningHeaders);
|
|
String dateHeader = headers.get("date");
|
|
Instant dateInstant = dateHeader != null ? ParseUtils.parseHeaderDate(dateHeader) : null;
|
|
Iterator<String> i = warningHeaders.iterator();
|
|
// Suppress incorrect warning header
|
|
while (i.hasNext()) {
|
|
String warningHeader = i.next();
|
|
Instant warningInstant = ParseUtils.parseWarningHeaderDate(warningHeader);
|
|
if (warningInstant != null && dateInstant != null && !warningInstant.equals(dateInstant)) {
|
|
i.remove();
|
|
}
|
|
}
|
|
}
|
|
proxiedResponse.putHeader("warning", warningHeaders);
|
|
|
|
// Handle other headers
|
|
headers.forEach(header -> {
|
|
String name = header.getKey();
|
|
String value = header.getValue();
|
|
if (name.equalsIgnoreCase("date") || name.equalsIgnoreCase("warning") || name.equalsIgnoreCase("transfer-encoding")) {
|
|
// Skip
|
|
} else {
|
|
proxiedResponse.headers().add(name, value);
|
|
}
|
|
});
|
|
|
|
//
|
|
if (body == null) {
|
|
proxiedResponse.end();
|
|
return;
|
|
}
|
|
|
|
long len = body.length();
|
|
if (len >= 0) {
|
|
proxiedResponse.putHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(len));
|
|
} else {
|
|
if (request.proxiedRequest().version() == HttpVersion.HTTP_1_0) {
|
|
// Special handling for HTTP 1.0 clients that cannot handle chunked encoding
|
|
// we need to buffer the content
|
|
BufferingWriteStream buffer = new BufferingWriteStream();
|
|
body.stream().pipeTo(buffer, ar -> {
|
|
if (ar.succeeded()) {
|
|
Buffer content = buffer.content();
|
|
proxiedResponse.end(content, completionHandler);
|
|
} else {
|
|
System.out.println("Not implemented");
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
proxiedResponse.setChunked(true);
|
|
}
|
|
ReadStream<Buffer> bodyStream = body.stream();
|
|
sendResponse(bodyStream, completionHandler);
|
|
}
|
|
|
|
@Override
|
|
public ProxyResponse release() {
|
|
if (response != null) {
|
|
response.resume();
|
|
response = null;
|
|
body = null;
|
|
headers.clear();
|
|
}
|
|
return this;
|
|
}
|
|
|
|
private void sendResponse(ReadStream<Buffer> body, Handler<AsyncResult<Void>> completionHandler) {
|
|
Pipe<Buffer> pipe = body.pipe();
|
|
pipe.endOnSuccess(true);
|
|
pipe.endOnFailure(false);
|
|
pipe.to(proxiedResponse, ar -> {
|
|
if (ar.failed()) {
|
|
request.request.reset();
|
|
proxiedResponse.reset();
|
|
}
|
|
completionHandler.handle(ar);
|
|
});
|
|
}
|
|
}
|