Path: blob/master/src/java.net.http/share/classes/jdk/internal/net/http/Http1Request.java
41171 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 userHeaders111systemHeaders = HttpHeaders.of(systemHeaders.map(), (k,v) -> uh.firstValue(k).isEmpty());112113// If we're sending this request through a tunnel,114// then don't send any preemptive proxy-* headers that115// the authentication filter may have saved in its116// cache.117collectHeaders1(sb, systemHeaders, nocookies);118119// If we're sending this request through a tunnel,120// don't send any user-supplied proxy-* headers121// to the target server.122collectHeaders1(sb, userHeaders, nocookies);123124// Gather all 'Cookie:' headers and concatenate their125// values in a single line.126collectCookies(sb, systemHeaders, userHeaders);127128// terminate headers129sb.append('\r').append('\n');130}131132// Concatenate any 'Cookie:' header in a single line, as mandated133// by RFC 6265, section 5.4:134//135// <<When the user agent generates an HTTP request, the user agent MUST136// NOT attach more than one Cookie header field.>>137//138// This constraint is relaxed for the HTTP/2 protocol, which139// explicitly allows sending multiple Cookie header fields.140// RFC 7540 section 8.1.2.5:141//142// <<To allow for better compression efficiency, the Cookie header143// field MAY be split into separate header fields, each with one or144// more cookie-pairs.>>145//146// This method will therefore concatenate multiple Cookie header field147// values into a single field, in a similar way than was implemented in148// the legacy HttpURLConnection.149//150// Note that at this point this method performs no further validation151// on the actual field-values, except to check that they do not contain152// any illegal character for header field values.153//154private void collectCookies(StringBuilder sb,155HttpHeaders system,156HttpHeaders user) {157List<String> systemList = system.allValues(COOKIE_HEADER);158List<String> userList = user.allValues(COOKIE_HEADER);159boolean found = false;160if (systemList != null) {161for (String cookie : systemList) {162if (!found) {163found = true;164sb.append(COOKIE_HEADER).append(':').append(' ');165} else {166sb.append(';').append(' ');167}168sb.append(cookie);169}170}171if (userList != null) {172for (String cookie : userList) {173if (!found) {174found = true;175sb.append(COOKIE_HEADER).append(':').append(' ');176} else {177sb.append(';').append(' ');178}179sb.append(cookie);180}181}182if (found) sb.append('\r').append('\n');183}184185private void collectHeaders1(StringBuilder sb,186HttpHeaders headers,187BiPredicate<String,String> filter) {188for (Map.Entry<String,List<String>> entry : headers.map().entrySet()) {189String key = entry.getKey();190List<String> values = entry.getValue();191for (String value : values) {192if (!filter.test(key, value))193continue;194sb.append(key).append(':').append(' ')195.append(value)196.append('\r').append('\n');197}198}199}200201private String getPathAndQuery(URI uri) {202String path = uri.getRawPath();203String query = uri.getRawQuery();204if (path == null || path.isEmpty()) {205path = "/";206}207if (query == null) {208query = "";209}210if (query.isEmpty()) {211return Utils.encode(path);212} else {213return Utils.encode(path + "?" + query);214}215}216217private String authorityString(InetSocketAddress addr) {218return addr.getHostString() + ":" + addr.getPort();219}220221private String hostString() {222URI uri = request.uri();223int port = uri.getPort();224String host = uri.getHost();225226boolean defaultPort;227if (port == -1) {228defaultPort = true;229} else if (request.secure()) {230defaultPort = port == 443;231} else {232defaultPort = port == 80;233}234235if (defaultPort) {236return host;237} else {238return host + ":" + Integer.toString(port);239}240}241242private String requestURI() {243URI uri = request.uri();244String method = request.method();245246if ((request.proxy() == null && !method.equals("CONNECT"))247|| request.isWebSocket()) {248return getPathAndQuery(uri);249}250if (request.secure()) {251if (request.method().equals("CONNECT")) {252// use authority for connect itself253return authorityString(request.authority());254} else {255// requests over tunnel do not require full URL256return getPathAndQuery(uri);257}258}259if (request.method().equals("CONNECT")) {260// use authority for connect itself261return authorityString(request.authority());262}263264return uri == null? authorityString(request.authority()) : uri.toString();265}266267private boolean finished;268269synchronized boolean finished() {270return finished;271}272273synchronized void setFinished() {274finished = true;275}276277List<ByteBuffer> headers() {278if (Log.requests() && request != null) {279Log.logRequest(request.toString());280}281String uriString = requestURI();282StringBuilder sb = new StringBuilder(64);283sb.append(request.method())284.append(' ')285.append(uriString)286.append(" HTTP/1.1\r\n");287288URI uri = request.uri();289if (uri != null) {290systemHeadersBuilder.setHeader("Host", hostString());291}292if (requestPublisher == null) {293// Not a user request, or maybe a method, e.g. GET, with no body.294contentLength = 0;295} else {296contentLength = requestPublisher.contentLength();297}298299if (contentLength == 0) {300systemHeadersBuilder.setHeader("Content-Length", "0");301} else if (contentLength > 0) {302systemHeadersBuilder.setHeader("Content-Length", Long.toString(contentLength));303streaming = false;304} else {305streaming = true;306systemHeadersBuilder.setHeader("Transfer-encoding", "chunked");307}308collectHeaders0(sb);309String hs = sb.toString();310logHeaders(hs);311ByteBuffer b = ByteBuffer.wrap(hs.getBytes(US_ASCII));312return List.of(b);313}314315Http1BodySubscriber continueRequest() {316Http1BodySubscriber subscriber;317if (streaming) {318subscriber = new StreamSubscriber();319requestPublisher.subscribe(subscriber);320} else {321if (contentLength == 0)322return null;323324subscriber = new FixedContentSubscriber();325requestPublisher.subscribe(subscriber);326}327return subscriber;328}329330final class StreamSubscriber extends Http1BodySubscriber {331332StreamSubscriber() { super(debug); }333334@Override335public void onSubscribe(Flow.Subscription subscription) {336if (isSubscribed()) {337Throwable t = new IllegalStateException("already subscribed");338http1Exchange.appendToOutgoing(t);339} else {340setSubscription(subscription);341}342}343344@Override345public void onNext(ByteBuffer item) {346Objects.requireNonNull(item);347if (complete) {348Throwable t = new IllegalStateException("subscription already completed");349http1Exchange.appendToOutgoing(t);350} else {351int chunklen = item.remaining();352ArrayList<ByteBuffer> l = new ArrayList<>(3);353l.add(getHeader(chunklen));354l.add(item);355l.add(ByteBuffer.wrap(CRLF));356http1Exchange.appendToOutgoing(l);357}358}359360@Override361public String currentStateMessage() {362return "streaming request body " + (complete ? "complete" : "incomplete");363}364365@Override366public void onError(Throwable throwable) {367if (complete)368return;369370cancelSubscription();371http1Exchange.appendToOutgoing(throwable);372}373374@Override375public void onComplete() {376if (complete) {377Throwable t = new IllegalStateException("subscription already completed");378http1Exchange.appendToOutgoing(t);379} else {380ArrayList<ByteBuffer> l = new ArrayList<>(2);381l.add(ByteBuffer.wrap(EMPTY_CHUNK_BYTES));382l.add(ByteBuffer.wrap(CRLF));383complete = true;384//setFinished();385http1Exchange.appendToOutgoing(l);386http1Exchange.appendToOutgoing(COMPLETED);387setFinished(); // TODO: before or after,? does it matter?388389}390}391}392393final class FixedContentSubscriber extends Http1BodySubscriber {394395private volatile long contentWritten;396FixedContentSubscriber() { super(debug); }397398@Override399public void onSubscribe(Flow.Subscription subscription) {400if (isSubscribed()) {401Throwable t = new IllegalStateException("already subscribed");402http1Exchange.appendToOutgoing(t);403} else {404setSubscription(subscription);405}406}407408@Override409public void onNext(ByteBuffer item) {410if (debug.on()) debug.log("onNext");411Objects.requireNonNull(item);412if (complete) {413Throwable t = new IllegalStateException("subscription already completed");414http1Exchange.appendToOutgoing(t);415} else {416long writing = item.remaining();417long written = (contentWritten += writing);418419if (written > contentLength) {420cancelSubscription();421String msg = connection.getConnectionFlow()422+ " [" + Thread.currentThread().getName() +"] "423+ "Too many bytes in request body. Expected: "424+ contentLength + ", got: " + written;425http1Exchange.appendToOutgoing(new IOException(msg));426} else {427http1Exchange.appendToOutgoing(List.of(item));428}429}430}431432@Override433public String currentStateMessage() {434return format("fixed content-length: %d, bytes sent: %d",435contentLength, contentWritten);436}437438@Override439public void onError(Throwable throwable) {440if (debug.on()) debug.log("onError");441if (complete) // TODO: error?442return;443444cancelSubscription();445http1Exchange.appendToOutgoing(throwable);446}447448@Override449public void onComplete() {450if (debug.on()) debug.log("onComplete");451if (complete) {452Throwable t = new IllegalStateException("subscription already completed");453http1Exchange.appendToOutgoing(t);454} else {455complete = true;456long written = contentWritten;457if (contentLength > written) {458cancelSubscription();459Throwable t = new IOException(connection.getConnectionFlow()460+ " [" + Thread.currentThread().getName() +"] "461+ "Too few bytes returned by the publisher ("462+ written + "/"463+ contentLength + ")");464http1Exchange.appendToOutgoing(t);465} else {466http1Exchange.appendToOutgoing(COMPLETED);467}468}469}470}471472private static final byte[] CRLF = {'\r', '\n'};473private static final byte[] EMPTY_CHUNK_BYTES = {'0', '\r', '\n'};474475/** Returns a header for a particular chunk size */476private static ByteBuffer getHeader(int size) {477String hexStr = Integer.toHexString(size);478byte[] hexBytes = hexStr.getBytes(US_ASCII);479byte[] header = new byte[hexStr.length()+2];480System.arraycopy(hexBytes, 0, header, 0, hexBytes.length);481header[hexBytes.length] = CRLF[0];482header[hexBytes.length+1] = CRLF[1];483return ByteBuffer.wrap(header);484}485486final Logger debug = Utils.getDebugLogger(this::toString, Utils.DEBUG);487488}489490491