< prev index next >
   1 /*
   2  * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  */
  24 package java.net.http;
  25 
  26 import java.io.IOException;
  27 import java.net.URI;
  28 import java.nio.ByteBuffer;
  29 import java.util.List;
  30 import java.util.Map;
  31 import java.util.Set;
  32 import java.net.InetSocketAddress;
  33 import java.net.http.HttpConnection.Mode;
  34 import java.nio.charset.StandardCharsets;
  35 import java.util.function.LongConsumer;
  36 import static java.nio.charset.StandardCharsets.US_ASCII;
  37 
  38 /**
  39  *  A HTTP/1.1 request.
  40  *
  41  * send() -> Writes the request + body to the given channel, in one blocking
  42  * operation.
  43  */
  44 class Http1Request {
  45 
  46     final HttpRequestImpl request;
  47     final HttpConnection chan;
  48     // Multiple buffers are used to hold different parts of request
  49     // See line 206 and below for description
  50     final ByteBuffer[] buffers;
  51     final HttpRequest.BodyProcessor requestProc;
  52     final HttpHeaders userHeaders;
  53     final HttpHeadersImpl systemHeaders;
  54     final LongConsumer flowController;
  55     boolean streaming;
  56     long contentLength;
  57 
  58     Http1Request(HttpRequestImpl request, HttpConnection connection)
  59         throws IOException
  60     {
  61         this.request = request;
  62         this.chan = connection;
  63         buffers = new ByteBuffer[5]; // TODO: check
  64         this.requestProc = request.requestProcessor();
  65         this.userHeaders = request.getUserHeaders();
  66         this.systemHeaders = request.getSystemHeaders();
  67         this.flowController = this::dummy;
  68     }
  69 
  70     private void logHeaders() throws IOException {
  71         StringBuilder sb = new StringBuilder(256);
  72         sb.append("REQUEST HEADERS:\r\n");
  73         collectHeaders1(sb, request, systemHeaders);
  74         collectHeaders1(sb, request, userHeaders);
  75         Log.logHeaders(sb.toString());
  76     }
  77 
  78     private void dummy(long x) {
  79         // not used in this class
  80     }
  81 
  82     private void collectHeaders0() throws IOException {
  83         if (Log.headers()) {
  84             logHeaders();
  85         }
  86         StringBuilder sb = new StringBuilder(256);
  87         collectHeaders1(sb, request, systemHeaders);
  88         collectHeaders1(sb, request, userHeaders);
  89         sb.append("\r\n");
  90         String headers = sb.toString();
  91         buffers[1] = ByteBuffer.wrap(headers.getBytes(StandardCharsets.US_ASCII));
  92     }
  93 
  94     private void collectHeaders1(StringBuilder sb,
  95                                  HttpRequestImpl request,
  96                                  HttpHeaders headers)
  97         throws IOException
  98     {
  99         Map<String,List<String>> h = headers.map();
 100         Set<Map.Entry<String,List<String>>> entries = h.entrySet();
 101 
 102         for (Map.Entry<String,List<String>> entry : entries) {
 103             String key = entry.getKey();
 104             sb.append(key).append(": ");
 105             List<String> values = entry.getValue();
 106             int num = values.size();
 107             for (String value : values) {
 108                 sb.append(value);
 109                 if (--num > 0) {
 110                     sb.append(',');
 111                 }
 112             }
 113             sb.append("\r\n");
 114         }
 115     }
 116 
 117     private String getPathAndQuery(URI uri) {
 118         String path = uri.getPath();
 119         String query = uri.getQuery();
 120         if (path == null || path.equals("")) {
 121             path = "/";
 122         }
 123         if (query == null) {
 124             query = "";
 125         }
 126         if (query.equals("")) {
 127             return path;
 128         } else {
 129             return path + "?" + query;
 130         }
 131     }
 132 
 133     private String authorityString(InetSocketAddress addr) {
 134         return addr.getHostString() + ":" + addr.getPort();
 135     }
 136 
 137     private String hostString() {
 138         URI uri = request.uri();
 139         int port = uri.getPort();
 140         String host = uri.getHost();
 141 
 142         boolean defaultPort;
 143         if (port == -1)
 144             defaultPort = true;
 145         else if (request.secure())
 146             defaultPort = port == 443;
 147         else
 148             defaultPort = port == 80;
 149 
 150         if (defaultPort)
 151             return host;
 152         else
 153             return host + ":" + Integer.toString(port);
 154     }
 155 
 156     private String requestURI() {
 157         URI uri = request.uri();
 158         String method = request.method();
 159 
 160         if ((request.proxy() == null && !method.equals("CONNECT"))
 161                 || request.isWebSocket()) {
 162             return getPathAndQuery(uri);
 163         }
 164         if (request.secure()) {
 165             if (request.method().equals("CONNECT")) {
 166                 // use authority for connect itself
 167                 return authorityString(request.authority());
 168             } else {
 169                 // requests over tunnel do not require full URL
 170                 return getPathAndQuery(uri);
 171             }
 172         }
 173         return uri == null? authorityString(request.authority()) : uri.toString();
 174     }
 175 
 176     void sendHeadersOnly() throws IOException {
 177         collectHeaders();
 178         chan.write(buffers, 0, 2);
 179     }
 180 
 181     void sendRequest() throws IOException {
 182         collectHeaders();
 183         chan.configureMode(Mode.BLOCKING);
 184         if (contentLength == 0) {
 185             chan.write(buffers, 0, 2);
 186         } else if (contentLength > 0) {
 187             writeFixedContent(true);
 188         } else {
 189             writeStreamedContent(true);
 190         }
 191         setFinished();
 192     }
 193 
 194     private boolean finished;
 195 
 196     synchronized boolean finished() {
 197         return  finished;
 198     }
 199 
 200     synchronized void setFinished() {
 201         finished = true;
 202     }
 203 
 204     private void collectHeaders() throws IOException {
 205         if (Log.requests() && request != null) {
 206             Log.logRequest(request.toString());
 207         }
 208         String uriString = requestURI();
 209         StringBuilder sb = new StringBuilder(64);
 210         sb.append(request.method())
 211           .append(' ')
 212           .append(uriString)
 213           .append(" HTTP/1.1\r\n");
 214         String cmd = sb.toString();
 215 
 216         buffers[0] = ByteBuffer.wrap(cmd.getBytes(StandardCharsets.US_ASCII));
 217         URI uri = request.uri();
 218         if (uri != null) {
 219             systemHeaders.setHeader("Host", hostString());
 220         }
 221         if (request == null) {
 222             // this is not a user request. No content
 223             contentLength = 0;
 224         } else {
 225             contentLength = requestProc.onRequestStart(request, flowController);
 226         }
 227 
 228         if (contentLength == 0) {
 229             systemHeaders.setHeader("Content-Length", "0");
 230             collectHeaders0();
 231         } else if (contentLength > 0) {
 232             /* [0] request line [1] headers [2] body  */
 233             systemHeaders.setHeader("Content-Length",
 234                                     Integer.toString((int) contentLength));
 235             streaming = false;
 236             collectHeaders0();
 237             buffers[2] = chan.getBuffer();
 238         } else {
 239             /* Chunked:
 240              *
 241              * [0] request line [1] headers [2] chunk header [3] chunk data [4]
 242              * final chunk header and trailing CRLF of previous chunks
 243              *
 244              * 2,3,4 used repeatedly */
 245             streaming = true;
 246             systemHeaders.setHeader("Transfer-encoding", "chunked");
 247             collectHeaders0();
 248             buffers[3] = chan.getBuffer();
 249         }
 250     }
 251 
 252     // The following two methods used by Http1Exchange to handle expect continue
 253 
 254     void continueRequest() throws IOException {
 255         if (streaming) {
 256             writeStreamedContent(false);
 257         } else {
 258             writeFixedContent(false);
 259         }
 260         setFinished();
 261     }
 262 
 263     /* Entire request is sent, or just body only  */
 264     private void writeStreamedContent(boolean includeHeaders)
 265         throws IOException
 266     {
 267         if (requestProc instanceof HttpRequest.BodyProcessor) {
 268             HttpRequest.BodyProcessor pullproc = requestProc;
 269             int startbuf, nbufs;
 270 
 271             if (includeHeaders) {
 272                 startbuf = 0;
 273                 nbufs = 5;
 274             } else {
 275                 startbuf = 2;
 276                 nbufs = 3;
 277             }
 278             try {
 279                 // TODO: currently each write goes out as one chunk
 280                 // TODO: should be collecting data and buffer it.
 281 
 282                 buffers[3].clear();
 283                 boolean done = pullproc.onRequestBodyChunk(buffers[3]);
 284                 int chunklen = buffers[3].position();
 285                 buffers[2] = getHeader(chunklen);
 286                 buffers[3].flip();
 287                 buffers[4] = CRLF_BUFFER();
 288                 chan.write(buffers, startbuf, nbufs);
 289                 while (!done) {
 290                     buffers[3].clear();
 291                     done = pullproc.onRequestBodyChunk(buffers[3]);
 292                     if (done)
 293                         break;
 294                     buffers[3].flip();
 295                     chunklen = buffers[3].remaining();
 296                     buffers[2] = getHeader(chunklen);
 297                     buffers[4] = CRLF_BUFFER();
 298                     chan.write(buffers, 2, 3);
 299                 }
 300                 buffers[3] = EMPTY_CHUNK_HEADER();
 301                 buffers[4] = CRLF_BUFFER();
 302                 chan.write(buffers, 3, 2);
 303             } catch (IOException e) {
 304                 requestProc.onRequestError(e);
 305                 throw e;
 306             }
 307         }
 308     }
 309     /* Entire request is sent, or just body only */
 310     private void writeFixedContent(boolean includeHeaders)
 311         throws IOException
 312     {
 313         try {
 314             int startbuf, nbufs;
 315 
 316             if (contentLength == 0) {
 317                 return;
 318             }
 319             if (includeHeaders) {
 320                 startbuf = 0;
 321                 nbufs = 3;
 322             } else {
 323                 startbuf = 2;
 324                 nbufs = 1;
 325                 buffers[0].clear().flip();
 326                 buffers[1].clear().flip();
 327             }
 328             buffers[2] = chan.getBuffer();
 329             if (requestProc instanceof HttpRequest.BodyProcessor) {
 330                 HttpRequest.BodyProcessor pullproc = requestProc;
 331 
 332                 boolean done = pullproc.onRequestBodyChunk(buffers[2]);
 333                 buffers[2].flip();
 334                 long headersLength = buffers[0].remaining() + buffers[1].remaining();
 335                 long contentWritten = buffers[2].remaining();
 336                 chan.checkWrite(headersLength + contentWritten,
 337                                 buffers,
 338                                 startbuf,
 339                                 nbufs);
 340                 while (!done) {
 341                     buffers[2].clear();
 342                     done = pullproc.onRequestBodyChunk(buffers[2]);
 343                     buffers[2].flip();
 344                     long len = buffers[2].remaining();
 345                     if (contentWritten + len > contentLength) {
 346                         break;
 347                     }
 348                     chan.checkWrite(len, buffers[2]);
 349                     contentWritten += len;
 350                 }
 351                 if (contentWritten != contentLength) {
 352                     throw new IOException("wrong content length");
 353                 }
 354             }
 355         } catch (IOException e) {
 356             requestProc.onRequestError(e);
 357             throw e;
 358         }
 359     }
 360 
 361     private static final byte[] CRLF = {'\r', '\n'};
 362     private static final byte[] EMPTY_CHUNK_BYTES = {'0', '\r', '\n'};
 363 
 364     private ByteBuffer CRLF_BUFFER() {
 365         return ByteBuffer.wrap(CRLF);
 366     }
 367 
 368     private ByteBuffer EMPTY_CHUNK_HEADER() {
 369         return ByteBuffer.wrap(EMPTY_CHUNK_BYTES);
 370     }
 371 
 372     /* Returns a header for a particular chunk size */
 373     private static ByteBuffer getHeader(int size){
 374         String hexStr =  Integer.toHexString(size);
 375         byte[] hexBytes = hexStr.getBytes(US_ASCII);
 376         byte[] header = new byte[hexStr.length()+2];
 377         System.arraycopy(hexBytes, 0, header, 0, hexBytes.length);
 378         header[hexBytes.length] = CRLF[0];
 379         header[hexBytes.length+1] = CRLF[1];
 380         return ByteBuffer.wrap(header);
 381     }
 382 }
< prev index next >