-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
HttpServerRequest.java
511 lines (453 loc) · 15.7 KB
/
HttpServerRequest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
/*
* Copyright (c) 2011-2019 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.core.http;
import io.netty.handler.codec.DecoderResult;
import io.netty.handler.codec.TooLongFrameException;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.vertx.codegen.annotations.*;
import io.vertx.core.AsyncResult;
import io.vertx.core.Context;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.MultiMap;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.net.NetSocket;
import io.vertx.core.net.SocketAddress;
import io.vertx.core.streams.ReadStream;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.security.cert.X509Certificate;
import java.util.Map;
import java.util.Objects;
/**
* Represents a server-side HTTP request.
* <p>
* Instances are created for each request and passed to the user via a handler.
* <p>
* Each instance of this class is associated with a corresponding {@link HttpServerResponse} instance via
* {@link #response}.<p>
* It implements {@link io.vertx.core.streams.ReadStream} so it can be used with
* {@link io.vertx.core.streams.Pipe} to pipe data with flow control.
* <p>
*
* @author <a href="http://tfox.org">Tim Fox</a>
*/
@VertxGen
public interface HttpServerRequest extends ReadStream<Buffer> {
/**
* The default invalid request handler, it uses uses the {@link #decoderResult()} cause and the request information
* to determine the status code of the response to be sent.
*
* <ul>
* <li>When the cause is an instance of {@code io.netty.handler.codec.TooLongFrameException} and the error message
* starts with <i>An HTTP line is larger than</i> the {@code REQUEST_URI_TOO_LONG} status is sent </li>
* <li>Otherwise when the cause is an instance of {@code io.netty.handler.codec.TooLongFrameException} and the error message
* starts with <i>HTTP header is larger than</i> the {@code REQUEST_HEADER_FIELDS_TOO_LARGE} status is sent</li>
* <li>Otherwise when the request is a {@link HttpVersion#HTTP_1_0} {@code GET} {@code /bad-request} then {@code BAD_REQUEST} status is sent</li>
* <li>Otherwise the connection is closed</li>
* </ul>
*/
@GenIgnore
Handler<HttpServerRequest> DEFAULT_INVALID_REQUEST_HANDLER = request -> {
DecoderResult result = request.decoderResult();
Throwable cause = result.cause();
HttpResponseStatus status = null;
if (cause instanceof TooLongFrameException) {
String causeMsg = cause.getMessage();
if (causeMsg.startsWith("An HTTP line is larger than")) {
status = HttpResponseStatus.REQUEST_URI_TOO_LONG;
} else if (causeMsg.startsWith("HTTP header is larger than")) {
status = HttpResponseStatus.REQUEST_HEADER_FIELDS_TOO_LARGE;
}
}
if (status == null && HttpMethod.GET == request.method() &&
HttpVersion.HTTP_1_0 == request.version() && "/bad-request".equals(request.uri())) {
// Matches Netty's specific HttpRequest for invalid messages
status = HttpResponseStatus.BAD_REQUEST;
}
if (status != null) {
request.response().setStatusCode(status.code()).end();
} else {
request.connection().close();
}
};
@Override
HttpServerRequest exceptionHandler(Handler<Throwable> handler);
@Override
HttpServerRequest handler(Handler<Buffer> handler);
@Override
HttpServerRequest pause();
@Override
HttpServerRequest resume();
@Override
HttpServerRequest fetch(long amount);
@Override
HttpServerRequest endHandler(Handler<Void> endHandler);
/**
* @return the HTTP version of the request
*/
HttpVersion version();
/**
* @return the HTTP method for the request.
*/
HttpMethod method();
/**
* @return true if this {@link io.vertx.core.net.NetSocket} is encrypted via SSL/TLS
*/
default boolean isSSL() {
return connection().isSsl();
}
/**
* @return the scheme of the request
*/
@Nullable
String scheme();
/**
* @return the URI of the request. This is usually a relative URI
*/
String uri();
/**
* @return The path part of the uri. For example /somepath/somemorepath/someresource.foo
*/
@Nullable
String path();
/**
* @return the query part of the uri. For example someparam=32&someotherparam=x
*/
@Nullable
String query();
/**
* @return the request host. For HTTP2 it returns the {@literal :authority} pseudo header otherwise it returns the {@literal Host} header
*/
@Nullable
String host();
/**
* @return the total number of bytes read for the body of the request.
*/
long bytesRead();
/**
* @return the response. Each instance of this class has an {@link HttpServerResponse} instance attached to it. This is used
* to send the response back to the client.
*/
@CacheReturn
HttpServerResponse response();
/**
* @return the headers in the request.
*/
@CacheReturn
MultiMap headers();
/**
* Return the first header value with the specified name
*
* @param headerName the header name
* @return the header value
*/
@Nullable
default String getHeader(String headerName) {
return headers().get(headerName);
}
/**
* Return the first header value with the specified name
*
* @param headerName the header name
* @return the header value
*/
@GenIgnore(GenIgnore.PERMITTED_TYPE)
default String getHeader(CharSequence headerName) {
return headers().get(headerName);
}
/**
* @return the query parameters in the request
*/
@CacheReturn
MultiMap params();
/**
* Return the first param value with the specified name
*
* @param paramName the param name
* @return the param value
*/
@Nullable
default String getParam(String paramName) {
return params().get(paramName);
}
/**
* Return the first param value with the specified name or {@code defaultValue} when the query param is not present
*
* @param paramName the param name
* @param defaultValue the default value, must be non-null
* @return the param value or {@code defaultValue} when not present
*/
default String getParam(String paramName, String defaultValue) {
Objects.requireNonNull(defaultValue, "defaultValue");
final String paramValue = params().get(paramName);
return paramValue != null ? paramValue : defaultValue;
}
/**
* @return the remote address for this connection, possibly {@code null} (e.g a server bound on a domain socket).
* If {@code useProxyProtocol} is set to {@code true}, the address returned will be of the actual connecting client.
*/
@CacheReturn
default SocketAddress remoteAddress() {
return connection().remoteAddress();
}
/**
* @return the local address for this connection, possibly {@code null} (e.g a server bound on a domain socket)
* If {@code useProxyProtocol} is set to {@code true}, the address returned will be of the proxy.
*/
@CacheReturn
default SocketAddress localAddress() {
return connection().localAddress();
}
/**
* @return SSLSession associated with the underlying socket. Returns null if connection is
* not SSL.
* @see javax.net.ssl.SSLSession
*/
@GenIgnore(GenIgnore.PERMITTED_TYPE)
default SSLSession sslSession() {
return connection().sslSession();
}
/**
* Note: Java SE 5+ recommends to use javax.net.ssl.SSLSession#getPeerCertificates() instead of
* of javax.net.ssl.SSLSession#getPeerCertificateChain() which this method is based on. Use {@link #sslSession()} to
* access that method.
*
* @return an ordered array of the peer certificates. Returns null if connection is
* not SSL.
* @throws javax.net.ssl.SSLPeerUnverifiedException SSL peer's identity has not been verified.
* @see javax.net.ssl.SSLSession#getPeerCertificateChain()
* @see #sslSession()
*/
@GenIgnore
X509Certificate[] peerCertificateChain() throws SSLPeerUnverifiedException;
/**
* @return the absolute URI corresponding to the HTTP request
*/
String absoluteURI();
/**
* Convenience method for receiving the entire request body in one piece.
* <p>
* This saves the user having to manually setting a data and end handler and append the chunks of the body until
* the whole body received. Don't use this if your request body is large - you could potentially run out of RAM.
*
* @param bodyHandler This handler will be called after all the body has been received
*/
@Fluent
default HttpServerRequest bodyHandler(@Nullable Handler<Buffer> bodyHandler) {
body().onSuccess(bodyHandler);
return this;
}
/**
* Same as {@link #body()} but with an {@code handler} called when the operation completes
*/
default HttpServerRequest body(Handler<AsyncResult<Buffer>> handler) {
body().onComplete(handler);
return this;
}
/**
* Convenience method for receiving the entire request body in one piece.
* <p>
* This saves you having to manually set a dataHandler and an endHandler and append the chunks of the body until
* the whole body received. Don't use this if your request body is large - you could potentially run out of RAM.
*
* @return a future completed with the body result
*/
Future<Buffer> body();
/**
* Same as {@link #end()} but with an {@code handler} called when the operation completes
*/
default void end(Handler<AsyncResult<Void>> handler) {
end().onComplete(handler);
}
/**
* Returns a future signaling when the request has been fully received successfully or failed.
*
* @return a future completed with the body result
*/
Future<Void> end();
/**
* Establish a TCP <a href="https://tools.ietf.org/html/rfc7231#section-4.3.6">tunnel<a/> with the client.
*
* <p> This must be called only for {@code CONNECT} HTTP method and before any response is sent.
*
* <p> Calling this sends a {@code 200} response with no {@code content-length} header set and
* then provides the {@code NetSocket} for handling the created tunnel. Any HTTP header set on the
* response before calling this method will be sent.
*
* <pre>
* server.requestHandler(req -> {
* if (req.method() == HttpMethod.CONNECT) {
* // Send a 200 response to accept the connect
* NetSocket socket = req.netSocket();
* socket.handler(buff -> {
* socket.write(buff);
* });
* }
* ...
* });
* </pre>
*
* @param handler the completion handler
*/
default void toNetSocket(Handler<AsyncResult<NetSocket>> handler) {
Future<NetSocket> fut = toNetSocket();
if (handler != null) {
fut.onComplete(handler);
}
}
/**
* Like {@link #toNetSocket(Handler)} but returns a {@code Future} of the asynchronous result
*/
Future<NetSocket> toNetSocket();
/**
* Call this with true if you are expecting a multi-part body to be submitted in the request.
* This must be called before the body of the request has been received
*
* @param expect true - if you are expecting a multi-part body
* @return a reference to this, so the API can be used fluently
*/
@Fluent
HttpServerRequest setExpectMultipart(boolean expect);
/**
* @return true if we are expecting a multi-part body for this request. See {@link #setExpectMultipart}.
*/
boolean isExpectMultipart();
/**
* Set an upload handler. The handler will get notified once a new file upload was received to allow you to deal
* with the file upload.
*
* @return a reference to this, so the API can be used fluently
*/
@Fluent
HttpServerRequest uploadHandler(@Nullable Handler<HttpServerFileUpload> uploadHandler);
/**
* Returns a map of all form attributes in the request.
* <p>
* Be aware that the attributes will only be available after the whole body has been received, i.e. after
* the request end handler has been called.
* <p>
* {@link #setExpectMultipart(boolean)} must be called first before trying to get the form attributes.
*
* @return the form attributes
*/
@CacheReturn
MultiMap formAttributes();
/**
* Return the first form attribute value with the specified name
*
* @param attributeName the attribute name
* @return the attribute value
*/
@Nullable
String getFormAttribute(String attributeName);
/**
* @return the id of the stream of this request, {@literal -1} when it is not yet determined, i.e
* the request has not been yet sent or it is not supported HTTP/1.x
*/
@CacheReturn
default int streamId() {
return -1;
}
/**
* Upgrade the connection of the current request to a WebSocket.
* <p>
* This is an alternative way of handling WebSockets and can only be used if no WebSocket handler is set on the
* {@code HttpServer}, and can only be used during the upgrade request during the WebSocket handshake.
*
* <p> Both {@link #handler(Handler)} and {@link #endHandler(Handler)} will be set to get the full body of the
* request that is necessary to perform the WebSocket handshake.
*
* <p> If you need to do an asynchronous upgrade, i.e not performed immediately in your request handler,
* you need to {@link #pause()} the request in order to not lose HTTP events necessary to upgrade the
* request.
*
* @param handler the completion handler
*/
default void toWebSocket(Handler<AsyncResult<ServerWebSocket>> handler) {
Future<ServerWebSocket> fut = toWebSocket();
if (handler != null) {
fut.onComplete(handler);
}
}
/**
* Like {@link #toWebSocket(Handler)} but returns a {@code Future} of the asynchronous result
*/
Future<ServerWebSocket> toWebSocket();
/**
* Has the request ended? I.e. has the entire request, including the body been read?
*
* @return true if ended
*/
boolean isEnded();
/**
* Set a custom frame handler. The handler will get notified when the http stream receives an custom HTTP/2
* frame. HTTP/2 permits extension of the protocol.
*
* @return a reference to this, so the API can be used fluently
*/
@Fluent
HttpServerRequest customFrameHandler(Handler<HttpFrame> handler);
/**
* @return the {@link HttpConnection} associated with this request
*/
@CacheReturn
HttpConnection connection();
/**
* @return the priority of the associated HTTP/2 stream for HTTP/2 otherwise {@code null}
*/
default StreamPriority streamPriority() {
return null;
}
/**
* Set an handler for stream priority changes
* <p>
* This is not implemented for HTTP/1.x.
*
* @param handler the handler to be called when stream priority changes
*/
@Fluent
HttpServerRequest streamPriorityHandler(Handler<StreamPriority> handler);
/**
* @return Netty's decoder result useful for handling invalid requests with {@link HttpServer#invalidRequestHandler}
*/
@GenIgnore
DecoderResult decoderResult();
/**
* Get the cookie with the specified name.
*
* @param name the cookie name
* @return the cookie
*/
default @Nullable Cookie getCookie(String name) {
return cookieMap().get(name);
}
/**
* @return the number of cookieMap.
*/
default int cookieCount() {
return cookieMap().size();
}
/**
* @return a map of all the cookies.
*/
Map<String, Cookie> cookieMap();
/**
* Marks this request as being routed to the given route. This is purely informational and is
* being provided to metrics.
*
* @param route The route this request has been routed to.
*/
@Fluent
default HttpServerRequest routed(String route) {
return this;
}
}