Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/jdk17u
Path: blob/master/src/java.net.http/share/classes/jdk/internal/net/http/Http1Request.java
67707 views
1
/*
2
* Copyright (c) 2015, 2019, 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
* questions.
24
*/
25
26
package jdk.internal.net.http;
27
28
import java.io.IOException;
29
import java.net.URI;
30
import java.net.http.HttpClient;
31
import java.nio.ByteBuffer;
32
import java.util.ArrayList;
33
import java.util.List;
34
import java.util.Map;
35
import java.net.InetSocketAddress;
36
import java.util.Objects;
37
import java.util.concurrent.Flow;
38
import java.util.function.BiPredicate;
39
import java.net.http.HttpHeaders;
40
import java.net.http.HttpRequest;
41
import jdk.internal.net.http.Http1Exchange.Http1BodySubscriber;
42
import jdk.internal.net.http.common.HttpHeadersBuilder;
43
import jdk.internal.net.http.common.Log;
44
import jdk.internal.net.http.common.Logger;
45
import jdk.internal.net.http.common.Utils;
46
47
import static java.lang.String.format;
48
import static java.nio.charset.StandardCharsets.US_ASCII;
49
50
/**
51
* An HTTP/1.1 request.
52
*/
53
class Http1Request {
54
55
private static final String COOKIE_HEADER = "Cookie";
56
private static final BiPredicate<String,String> NOCOOKIES =
57
(k,v) -> !COOKIE_HEADER.equalsIgnoreCase(k);
58
59
private final HttpRequestImpl request;
60
private final Http1Exchange<?> http1Exchange;
61
private final HttpConnection connection;
62
private final HttpRequest.BodyPublisher requestPublisher;
63
private volatile HttpHeaders userHeaders;
64
private final HttpHeadersBuilder systemHeadersBuilder;
65
private volatile boolean streaming;
66
private volatile long contentLength;
67
68
Http1Request(HttpRequestImpl request,
69
Http1Exchange<?> http1Exchange)
70
throws IOException
71
{
72
this.request = request;
73
this.http1Exchange = http1Exchange;
74
this.connection = http1Exchange.connection();
75
this.requestPublisher = request.requestPublisher; // may be null
76
this.userHeaders = request.getUserHeaders();
77
this.systemHeadersBuilder = request.getSystemHeadersBuilder();
78
}
79
80
private void logHeaders(String completeHeaders) {
81
if (Log.headers()) {
82
//StringBuilder sb = new StringBuilder(256);
83
//sb.append("REQUEST HEADERS:\n");
84
//Log.dumpHeaders(sb, " ", systemHeaders);
85
//Log.dumpHeaders(sb, " ", userHeaders);
86
//Log.logHeaders(sb.toString());
87
88
String s = completeHeaders.replaceAll("\r\n", "\n");
89
if (s.endsWith("\n\n")) s = s.substring(0, s.length() - 2);
90
Log.logHeaders("REQUEST HEADERS:\n{0}\n", s);
91
}
92
}
93
94
95
public void collectHeaders0(StringBuilder sb) {
96
BiPredicate<String,String> filter =
97
connection.headerFilter(request);
98
99
// Filter out 'Cookie:' headers, we will collect them at the end.
100
BiPredicate<String,String> nocookies = NOCOOKIES.and(filter);
101
102
HttpHeaders systemHeaders = systemHeadersBuilder.build();
103
HttpClient client = http1Exchange.client();
104
105
// Filter overridable headers from userHeaders
106
userHeaders = HttpHeaders.of(userHeaders.map(),
107
connection.contextRestricted(request, client));
108
109
final HttpHeaders uh = userHeaders;
110
111
// Filter any headers from systemHeaders that are set in userHeaders
112
final HttpHeaders sh = HttpHeaders.of(systemHeaders.map(),
113
(k,v) -> uh.firstValue(k).isEmpty());
114
115
// If we're sending this request through a tunnel,
116
// then don't send any preemptive proxy-* headers that
117
// the authentication filter may have saved in its
118
// cache.
119
collectHeaders1(sb, sh, nocookies);
120
121
// If we're sending this request through a tunnel,
122
// don't send any user-supplied proxy-* headers
123
// to the target server.
124
collectHeaders1(sb, uh, nocookies);
125
126
// Gather all 'Cookie:' headers from the unfiltered system headers,
127
// and the user headers, and concatenate their values in a single line
128
collectCookies(sb, systemHeaders, userHeaders);
129
130
// terminate headers
131
sb.append('\r').append('\n');
132
}
133
134
// Concatenate any 'Cookie:' header in a single line, as mandated
135
// by RFC 6265, section 5.4:
136
//
137
// <<When the user agent generates an HTTP request, the user agent MUST
138
// NOT attach more than one Cookie header field.>>
139
//
140
// This constraint is relaxed for the HTTP/2 protocol, which
141
// explicitly allows sending multiple Cookie header fields.
142
// RFC 7540 section 8.1.2.5:
143
//
144
// <<To allow for better compression efficiency, the Cookie header
145
// field MAY be split into separate header fields, each with one or
146
// more cookie-pairs.>>
147
//
148
// This method will therefore concatenate multiple Cookie header field
149
// values into a single field, in a similar way than was implemented in
150
// the legacy HttpURLConnection.
151
//
152
// Note that at this point this method performs no further validation
153
// on the actual field-values, except to check that they do not contain
154
// any illegal character for header field values.
155
//
156
private void collectCookies(StringBuilder sb,
157
HttpHeaders system,
158
HttpHeaders user) {
159
List<String> systemList = system.allValues(COOKIE_HEADER);
160
List<String> userList = user.allValues(COOKIE_HEADER);
161
boolean found = false;
162
if (systemList != null) {
163
for (String cookie : systemList) {
164
if (!found) {
165
found = true;
166
sb.append(COOKIE_HEADER).append(':').append(' ');
167
} else {
168
sb.append(';').append(' ');
169
}
170
sb.append(cookie);
171
}
172
}
173
if (userList != null) {
174
for (String cookie : userList) {
175
if (!found) {
176
found = true;
177
sb.append(COOKIE_HEADER).append(':').append(' ');
178
} else {
179
sb.append(';').append(' ');
180
}
181
sb.append(cookie);
182
}
183
}
184
if (found) sb.append('\r').append('\n');
185
}
186
187
private void collectHeaders1(StringBuilder sb,
188
HttpHeaders headers,
189
BiPredicate<String,String> filter) {
190
for (Map.Entry<String,List<String>> entry : headers.map().entrySet()) {
191
String key = entry.getKey();
192
List<String> values = entry.getValue();
193
for (String value : values) {
194
if (!filter.test(key, value))
195
continue;
196
sb.append(key).append(':').append(' ')
197
.append(value)
198
.append('\r').append('\n');
199
}
200
}
201
}
202
203
private String getPathAndQuery(URI uri) {
204
String path = uri.getRawPath();
205
String query = uri.getRawQuery();
206
if (path == null || path.isEmpty()) {
207
path = "/";
208
}
209
if (query == null) {
210
query = "";
211
}
212
if (query.isEmpty()) {
213
return Utils.encode(path);
214
} else {
215
return Utils.encode(path + "?" + query);
216
}
217
}
218
219
private String authorityString(InetSocketAddress addr) {
220
return addr.getHostString() + ":" + addr.getPort();
221
}
222
223
private String hostString() {
224
URI uri = request.uri();
225
int port = uri.getPort();
226
String host = uri.getHost();
227
228
boolean defaultPort;
229
if (port == -1) {
230
defaultPort = true;
231
} else if (request.secure()) {
232
defaultPort = port == 443;
233
} else {
234
defaultPort = port == 80;
235
}
236
237
if (defaultPort) {
238
return host;
239
} else {
240
return host + ":" + Integer.toString(port);
241
}
242
}
243
244
private String requestURI() {
245
URI uri = request.uri();
246
String method = request.method();
247
248
if ((request.proxy() == null && !method.equals("CONNECT"))
249
|| request.isWebSocket()) {
250
return getPathAndQuery(uri);
251
}
252
if (request.secure()) {
253
if (request.method().equals("CONNECT")) {
254
// use authority for connect itself
255
return authorityString(request.authority());
256
} else {
257
// requests over tunnel do not require full URL
258
return getPathAndQuery(uri);
259
}
260
}
261
if (request.method().equals("CONNECT")) {
262
// use authority for connect itself
263
return authorityString(request.authority());
264
}
265
266
return uri == null? authorityString(request.authority()) : uri.toString();
267
}
268
269
private boolean finished;
270
271
synchronized boolean finished() {
272
return finished;
273
}
274
275
synchronized void setFinished() {
276
finished = true;
277
}
278
279
List<ByteBuffer> headers() {
280
if (Log.requests() && request != null) {
281
Log.logRequest(request.toString());
282
}
283
String uriString = requestURI();
284
StringBuilder sb = new StringBuilder(64);
285
sb.append(request.method())
286
.append(' ')
287
.append(uriString)
288
.append(" HTTP/1.1\r\n");
289
290
URI uri = request.uri();
291
if (uri != null) {
292
systemHeadersBuilder.setHeader("Host", hostString());
293
}
294
if (requestPublisher == null) {
295
// Not a user request, or maybe a method, e.g. GET, with no body.
296
contentLength = 0;
297
} else {
298
contentLength = requestPublisher.contentLength();
299
}
300
301
if (contentLength == 0) {
302
systemHeadersBuilder.setHeader("Content-Length", "0");
303
} else if (contentLength > 0) {
304
systemHeadersBuilder.setHeader("Content-Length", Long.toString(contentLength));
305
streaming = false;
306
} else {
307
streaming = true;
308
systemHeadersBuilder.setHeader("Transfer-encoding", "chunked");
309
}
310
collectHeaders0(sb);
311
String hs = sb.toString();
312
logHeaders(hs);
313
ByteBuffer b = ByteBuffer.wrap(hs.getBytes(US_ASCII));
314
return List.of(b);
315
}
316
317
Http1BodySubscriber continueRequest() {
318
Http1BodySubscriber subscriber;
319
if (streaming) {
320
subscriber = new StreamSubscriber();
321
requestPublisher.subscribe(subscriber);
322
} else {
323
if (contentLength == 0)
324
return null;
325
326
subscriber = new FixedContentSubscriber();
327
requestPublisher.subscribe(subscriber);
328
}
329
return subscriber;
330
}
331
332
final class StreamSubscriber extends Http1BodySubscriber {
333
334
StreamSubscriber() { super(debug); }
335
336
@Override
337
public void onSubscribe(Flow.Subscription subscription) {
338
if (isSubscribed()) {
339
Throwable t = new IllegalStateException("already subscribed");
340
http1Exchange.appendToOutgoing(t);
341
} else {
342
setSubscription(subscription);
343
}
344
}
345
346
@Override
347
public void onNext(ByteBuffer item) {
348
Objects.requireNonNull(item);
349
if (complete) {
350
Throwable t = new IllegalStateException("subscription already completed");
351
http1Exchange.appendToOutgoing(t);
352
} else {
353
int chunklen = item.remaining();
354
ArrayList<ByteBuffer> l = new ArrayList<>(3);
355
l.add(getHeader(chunklen));
356
l.add(item);
357
l.add(ByteBuffer.wrap(CRLF));
358
http1Exchange.appendToOutgoing(l);
359
}
360
}
361
362
@Override
363
public String currentStateMessage() {
364
return "streaming request body " + (complete ? "complete" : "incomplete");
365
}
366
367
@Override
368
public void onError(Throwable throwable) {
369
if (complete)
370
return;
371
372
cancelSubscription();
373
http1Exchange.appendToOutgoing(throwable);
374
}
375
376
@Override
377
public void onComplete() {
378
if (complete) {
379
Throwable t = new IllegalStateException("subscription already completed");
380
http1Exchange.appendToOutgoing(t);
381
} else {
382
ArrayList<ByteBuffer> l = new ArrayList<>(2);
383
l.add(ByteBuffer.wrap(EMPTY_CHUNK_BYTES));
384
l.add(ByteBuffer.wrap(CRLF));
385
complete = true;
386
//setFinished();
387
http1Exchange.appendToOutgoing(l);
388
http1Exchange.appendToOutgoing(COMPLETED);
389
setFinished(); // TODO: before or after,? does it matter?
390
391
}
392
}
393
}
394
395
final class FixedContentSubscriber extends Http1BodySubscriber {
396
397
private volatile long contentWritten;
398
FixedContentSubscriber() { super(debug); }
399
400
@Override
401
public void onSubscribe(Flow.Subscription subscription) {
402
if (isSubscribed()) {
403
Throwable t = new IllegalStateException("already subscribed");
404
http1Exchange.appendToOutgoing(t);
405
} else {
406
setSubscription(subscription);
407
}
408
}
409
410
@Override
411
public void onNext(ByteBuffer item) {
412
if (debug.on()) debug.log("onNext");
413
Objects.requireNonNull(item);
414
if (complete) {
415
Throwable t = new IllegalStateException("subscription already completed");
416
http1Exchange.appendToOutgoing(t);
417
} else {
418
long writing = item.remaining();
419
long written = (contentWritten += writing);
420
421
if (written > contentLength) {
422
cancelSubscription();
423
String msg = connection.getConnectionFlow()
424
+ " [" + Thread.currentThread().getName() +"] "
425
+ "Too many bytes in request body. Expected: "
426
+ contentLength + ", got: " + written;
427
http1Exchange.appendToOutgoing(new IOException(msg));
428
} else {
429
http1Exchange.appendToOutgoing(List.of(item));
430
}
431
}
432
}
433
434
@Override
435
public String currentStateMessage() {
436
return format("fixed content-length: %d, bytes sent: %d",
437
contentLength, contentWritten);
438
}
439
440
@Override
441
public void onError(Throwable throwable) {
442
if (debug.on()) debug.log("onError");
443
if (complete) // TODO: error?
444
return;
445
446
cancelSubscription();
447
http1Exchange.appendToOutgoing(throwable);
448
}
449
450
@Override
451
public void onComplete() {
452
if (debug.on()) debug.log("onComplete");
453
if (complete) {
454
Throwable t = new IllegalStateException("subscription already completed");
455
http1Exchange.appendToOutgoing(t);
456
} else {
457
complete = true;
458
long written = contentWritten;
459
if (contentLength > written) {
460
cancelSubscription();
461
Throwable t = new IOException(connection.getConnectionFlow()
462
+ " [" + Thread.currentThread().getName() +"] "
463
+ "Too few bytes returned by the publisher ("
464
+ written + "/"
465
+ contentLength + ")");
466
http1Exchange.appendToOutgoing(t);
467
} else {
468
http1Exchange.appendToOutgoing(COMPLETED);
469
}
470
}
471
}
472
}
473
474
private static final byte[] CRLF = {'\r', '\n'};
475
private static final byte[] EMPTY_CHUNK_BYTES = {'0', '\r', '\n'};
476
477
/** Returns a header for a particular chunk size */
478
private static ByteBuffer getHeader(int size) {
479
String hexStr = Integer.toHexString(size);
480
byte[] hexBytes = hexStr.getBytes(US_ASCII);
481
byte[] header = new byte[hexStr.length()+2];
482
System.arraycopy(hexBytes, 0, header, 0, hexBytes.length);
483
header[hexBytes.length] = CRLF[0];
484
header[hexBytes.length+1] = CRLF[1];
485
return ByteBuffer.wrap(header);
486
}
487
488
final Logger debug = Utils.getDebugLogger(this::toString, Utils.DEBUG);
489
490
}
491
492