< 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 >