Path: blob/master/src/java.net.http/share/classes/jdk/internal/net/http/Http1Request.java
67707 views
/*1* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package jdk.internal.net.http;2627import java.io.IOException;28import java.net.URI;29import java.net.http.HttpClient;30import java.nio.ByteBuffer;31import java.util.ArrayList;32import java.util.List;33import java.util.Map;34import java.net.InetSocketAddress;35import java.util.Objects;36import java.util.concurrent.Flow;37import java.util.function.BiPredicate;38import java.net.http.HttpHeaders;39import java.net.http.HttpRequest;40import jdk.internal.net.http.Http1Exchange.Http1BodySubscriber;41import jdk.internal.net.http.common.HttpHeadersBuilder;42import jdk.internal.net.http.common.Log;43import jdk.internal.net.http.common.Logger;44import jdk.internal.net.http.common.Utils;4546import static java.lang.String.format;47import static java.nio.charset.StandardCharsets.US_ASCII;4849/**50* An HTTP/1.1 request.51*/52class Http1Request {5354private static final String COOKIE_HEADER = "Cookie";55private static final BiPredicate<String,String> NOCOOKIES =56(k,v) -> !COOKIE_HEADER.equalsIgnoreCase(k);5758private final HttpRequestImpl request;59private final Http1Exchange<?> http1Exchange;60private final HttpConnection connection;61private final HttpRequest.BodyPublisher requestPublisher;62private volatile HttpHeaders userHeaders;63private final HttpHeadersBuilder systemHeadersBuilder;64private volatile boolean streaming;65private volatile long contentLength;6667Http1Request(HttpRequestImpl request,68Http1Exchange<?> http1Exchange)69throws IOException70{71this.request = request;72this.http1Exchange = http1Exchange;73this.connection = http1Exchange.connection();74this.requestPublisher = request.requestPublisher; // may be null75this.userHeaders = request.getUserHeaders();76this.systemHeadersBuilder = request.getSystemHeadersBuilder();77}7879private void logHeaders(String completeHeaders) {80if (Log.headers()) {81//StringBuilder sb = new StringBuilder(256);82//sb.append("REQUEST HEADERS:\n");83//Log.dumpHeaders(sb, " ", systemHeaders);84//Log.dumpHeaders(sb, " ", userHeaders);85//Log.logHeaders(sb.toString());8687String s = completeHeaders.replaceAll("\r\n", "\n");88if (s.endsWith("\n\n")) s = s.substring(0, s.length() - 2);89Log.logHeaders("REQUEST HEADERS:\n{0}\n", s);90}91}929394public void collectHeaders0(StringBuilder sb) {95BiPredicate<String,String> filter =96connection.headerFilter(request);9798// Filter out 'Cookie:' headers, we will collect them at the end.99BiPredicate<String,String> nocookies = NOCOOKIES.and(filter);100101HttpHeaders systemHeaders = systemHeadersBuilder.build();102HttpClient client = http1Exchange.client();103104// Filter overridable headers from userHeaders105userHeaders = HttpHeaders.of(userHeaders.map(),106connection.contextRestricted(request, client));107108final HttpHeaders uh = userHeaders;109110// Filter any headers from systemHeaders that are set in userHeaders111final HttpHeaders sh = HttpHeaders.of(systemHeaders.map(),112(k,v) -> uh.firstValue(k).isEmpty());113114// If we're sending this request through a tunnel,115// then don't send any preemptive proxy-* headers that116// the authentication filter may have saved in its117// cache.118collectHeaders1(sb, sh, nocookies);119120// If we're sending this request through a tunnel,121// don't send any user-supplied proxy-* headers122// to the target server.123collectHeaders1(sb, uh, nocookies);124125// Gather all 'Cookie:' headers from the unfiltered system headers,126// and the user headers, and concatenate their values in a single line127collectCookies(sb, systemHeaders, userHeaders);128129// terminate headers130sb.append('\r').append('\n');131}132133// Concatenate any 'Cookie:' header in a single line, as mandated134// by RFC 6265, section 5.4:135//136// <<When the user agent generates an HTTP request, the user agent MUST137// NOT attach more than one Cookie header field.>>138//139// This constraint is relaxed for the HTTP/2 protocol, which140// explicitly allows sending multiple Cookie header fields.141// RFC 7540 section 8.1.2.5:142//143// <<To allow for better compression efficiency, the Cookie header144// field MAY be split into separate header fields, each with one or145// more cookie-pairs.>>146//147// This method will therefore concatenate multiple Cookie header field148// values into a single field, in a similar way than was implemented in149// the legacy HttpURLConnection.150//151// Note that at this point this method performs no further validation152// on the actual field-values, except to check that they do not contain153// any illegal character for header field values.154//155private void collectCookies(StringBuilder sb,156HttpHeaders system,157HttpHeaders user) {158List<String> systemList = system.allValues(COOKIE_HEADER);159List<String> userList = user.allValues(COOKIE_HEADER);160boolean found = false;161if (systemList != null) {162for (String cookie : systemList) {163if (!found) {164found = true;165sb.append(COOKIE_HEADER).append(':').append(' ');166} else {167sb.append(';').append(' ');168}169sb.append(cookie);170}171}172if (userList != null) {173for (String cookie : userList) {174if (!found) {175found = true;176sb.append(COOKIE_HEADER).append(':').append(' ');177} else {178sb.append(';').append(' ');179}180sb.append(cookie);181}182}183if (found) sb.append('\r').append('\n');184}185186private void collectHeaders1(StringBuilder sb,187HttpHeaders headers,188BiPredicate<String,String> filter) {189for (Map.Entry<String,List<String>> entry : headers.map().entrySet()) {190String key = entry.getKey();191List<String> values = entry.getValue();192for (String value : values) {193if (!filter.test(key, value))194continue;195sb.append(key).append(':').append(' ')196.append(value)197.append('\r').append('\n');198}199}200}201202private String getPathAndQuery(URI uri) {203String path = uri.getRawPath();204String query = uri.getRawQuery();205if (path == null || path.isEmpty()) {206path = "/";207}208if (query == null) {209query = "";210}211if (query.isEmpty()) {212return Utils.encode(path);213} else {214return Utils.encode(path + "?" + query);215}216}217218private String authorityString(InetSocketAddress addr) {219return addr.getHostString() + ":" + addr.getPort();220}221222private String hostString() {223URI uri = request.uri();224int port = uri.getPort();225String host = uri.getHost();226227boolean defaultPort;228if (port == -1) {229defaultPort = true;230} else if (request.secure()) {231defaultPort = port == 443;232} else {233defaultPort = port == 80;234}235236if (defaultPort) {237return host;238} else {239return host + ":" + Integer.toString(port);240}241}242243private String requestURI() {244URI uri = request.uri();245String method = request.method();246247if ((request.proxy() == null && !method.equals("CONNECT"))248|| request.isWebSocket()) {249return getPathAndQuery(uri);250}251if (request.secure()) {252if (request.method().equals("CONNECT")) {253// use authority for connect itself254return authorityString(request.authority());255} else {256// requests over tunnel do not require full URL257return getPathAndQuery(uri);258}259}260if (request.method().equals("CONNECT")) {261// use authority for connect itself262return authorityString(request.authority());263}264265return uri == null? authorityString(request.authority()) : uri.toString();266}267268private boolean finished;269270synchronized boolean finished() {271return finished;272}273274synchronized void setFinished() {275finished = true;276}277278List<ByteBuffer> headers() {279if (Log.requests() && request != null) {280Log.logRequest(request.toString());281}282String uriString = requestURI();283StringBuilder sb = new StringBuilder(64);284sb.append(request.method())285.append(' ')286.append(uriString)287.append(" HTTP/1.1\r\n");288289URI uri = request.uri();290if (uri != null) {291systemHeadersBuilder.setHeader("Host", hostString());292}293if (requestPublisher == null) {294// Not a user request, or maybe a method, e.g. GET, with no body.295contentLength = 0;296} else {297contentLength = requestPublisher.contentLength();298}299300if (contentLength == 0) {301systemHeadersBuilder.setHeader("Content-Length", "0");302} else if (contentLength > 0) {303systemHeadersBuilder.setHeader("Content-Length", Long.toString(contentLength));304streaming = false;305} else {306streaming = true;307systemHeadersBuilder.setHeader("Transfer-encoding", "chunked");308}309collectHeaders0(sb);310String hs = sb.toString();311logHeaders(hs);312ByteBuffer b = ByteBuffer.wrap(hs.getBytes(US_ASCII));313return List.of(b);314}315316Http1BodySubscriber continueRequest() {317Http1BodySubscriber subscriber;318if (streaming) {319subscriber = new StreamSubscriber();320requestPublisher.subscribe(subscriber);321} else {322if (contentLength == 0)323return null;324325subscriber = new FixedContentSubscriber();326requestPublisher.subscribe(subscriber);327}328return subscriber;329}330331final class StreamSubscriber extends Http1BodySubscriber {332333StreamSubscriber() { super(debug); }334335@Override336public void onSubscribe(Flow.Subscription subscription) {337if (isSubscribed()) {338Throwable t = new IllegalStateException("already subscribed");339http1Exchange.appendToOutgoing(t);340} else {341setSubscription(subscription);342}343}344345@Override346public void onNext(ByteBuffer item) {347Objects.requireNonNull(item);348if (complete) {349Throwable t = new IllegalStateException("subscription already completed");350http1Exchange.appendToOutgoing(t);351} else {352int chunklen = item.remaining();353ArrayList<ByteBuffer> l = new ArrayList<>(3);354l.add(getHeader(chunklen));355l.add(item);356l.add(ByteBuffer.wrap(CRLF));357http1Exchange.appendToOutgoing(l);358}359}360361@Override362public String currentStateMessage() {363return "streaming request body " + (complete ? "complete" : "incomplete");364}365366@Override367public void onError(Throwable throwable) {368if (complete)369return;370371cancelSubscription();372http1Exchange.appendToOutgoing(throwable);373}374375@Override376public void onComplete() {377if (complete) {378Throwable t = new IllegalStateException("subscription already completed");379http1Exchange.appendToOutgoing(t);380} else {381ArrayList<ByteBuffer> l = new ArrayList<>(2);382l.add(ByteBuffer.wrap(EMPTY_CHUNK_BYTES));383l.add(ByteBuffer.wrap(CRLF));384complete = true;385//setFinished();386http1Exchange.appendToOutgoing(l);387http1Exchange.appendToOutgoing(COMPLETED);388setFinished(); // TODO: before or after,? does it matter?389390}391}392}393394final class FixedContentSubscriber extends Http1BodySubscriber {395396private volatile long contentWritten;397FixedContentSubscriber() { super(debug); }398399@Override400public void onSubscribe(Flow.Subscription subscription) {401if (isSubscribed()) {402Throwable t = new IllegalStateException("already subscribed");403http1Exchange.appendToOutgoing(t);404} else {405setSubscription(subscription);406}407}408409@Override410public void onNext(ByteBuffer item) {411if (debug.on()) debug.log("onNext");412Objects.requireNonNull(item);413if (complete) {414Throwable t = new IllegalStateException("subscription already completed");415http1Exchange.appendToOutgoing(t);416} else {417long writing = item.remaining();418long written = (contentWritten += writing);419420if (written > contentLength) {421cancelSubscription();422String msg = connection.getConnectionFlow()423+ " [" + Thread.currentThread().getName() +"] "424+ "Too many bytes in request body. Expected: "425+ contentLength + ", got: " + written;426http1Exchange.appendToOutgoing(new IOException(msg));427} else {428http1Exchange.appendToOutgoing(List.of(item));429}430}431}432433@Override434public String currentStateMessage() {435return format("fixed content-length: %d, bytes sent: %d",436contentLength, contentWritten);437}438439@Override440public void onError(Throwable throwable) {441if (debug.on()) debug.log("onError");442if (complete) // TODO: error?443return;444445cancelSubscription();446http1Exchange.appendToOutgoing(throwable);447}448449@Override450public void onComplete() {451if (debug.on()) debug.log("onComplete");452if (complete) {453Throwable t = new IllegalStateException("subscription already completed");454http1Exchange.appendToOutgoing(t);455} else {456complete = true;457long written = contentWritten;458if (contentLength > written) {459cancelSubscription();460Throwable t = new IOException(connection.getConnectionFlow()461+ " [" + Thread.currentThread().getName() +"] "462+ "Too few bytes returned by the publisher ("463+ written + "/"464+ contentLength + ")");465http1Exchange.appendToOutgoing(t);466} else {467http1Exchange.appendToOutgoing(COMPLETED);468}469}470}471}472473private static final byte[] CRLF = {'\r', '\n'};474private static final byte[] EMPTY_CHUNK_BYTES = {'0', '\r', '\n'};475476/** Returns a header for a particular chunk size */477private static ByteBuffer getHeader(int size) {478String hexStr = Integer.toHexString(size);479byte[] hexBytes = hexStr.getBytes(US_ASCII);480byte[] header = new byte[hexStr.length()+2];481System.arraycopy(hexBytes, 0, header, 0, hexBytes.length);482header[hexBytes.length] = CRLF[0];483header[hexBytes.length+1] = CRLF[1];484return ByteBuffer.wrap(header);485}486487final Logger debug = Utils.getDebugLogger(this::toString, Utils.DEBUG);488489}490491492