/* * 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 send() { Promise promise = request.context.promise(); send(promise); return promise.future(); } public void send(Handler> 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 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 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 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 body, Handler> completionHandler) { Pipe pipe = body.pipe(); pipe.endOnSuccess(true); pipe.endOnFailure(false); pipe.to(proxiedResponse, ar -> { if (ar.failed()) { request.request.reset(); proxiedResponse.reset(); } completionHandler.handle(ar); }); } }