-
Notifications
You must be signed in to change notification settings - Fork 3.9k
netty: Netty server poorly handles unknown content type #3735
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,6 +32,8 @@ | |
| import com.google.common.annotations.VisibleForTesting; | ||
| import com.google.common.base.Preconditions; | ||
| import io.grpc.Attributes; | ||
| import io.grpc.InternalMetadata; | ||
| import io.grpc.InternalStatus; | ||
| import io.grpc.Metadata; | ||
| import io.grpc.ServerStreamTracer; | ||
| import io.grpc.Status; | ||
|
|
@@ -54,6 +56,7 @@ | |
| import io.netty.handler.codec.http2.DefaultHttp2ConnectionEncoder; | ||
| import io.netty.handler.codec.http2.DefaultHttp2FrameReader; | ||
| import io.netty.handler.codec.http2.DefaultHttp2FrameWriter; | ||
| import io.netty.handler.codec.http2.DefaultHttp2Headers; | ||
| import io.netty.handler.codec.http2.DefaultHttp2LocalFlowController; | ||
| import io.netty.handler.codec.http2.DefaultHttp2RemoteFlowController; | ||
| import io.netty.handler.codec.http2.Http2Connection; | ||
|
|
@@ -375,15 +378,49 @@ private void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers | |
| throws Http2Exception { | ||
| if (!teWarningLogged && !TE_TRAILERS.equals(headers.get(TE_HEADER))) { | ||
| logger.warning(String.format("Expected header TE: %s, but %s is received. This means " | ||
| + "some intermediate proxy may not support trailers", | ||
| TE_TRAILERS, headers.get(TE_HEADER))); | ||
| + "some intermediate proxy may not support trailers", | ||
| TE_TRAILERS, headers.get(TE_HEADER))); | ||
| teWarningLogged = true; | ||
| } | ||
|
|
||
| try { | ||
|
|
||
| // Remove the leading slash of the path and get the fully qualified method name | ||
| CharSequence path = headers.path(); | ||
|
|
||
| if (path == null) { | ||
| respondWithHttpError(ctx, streamId, 404, Status.Code.UNIMPLEMENTED, | ||
| "Expected path but is missing"); | ||
| return; | ||
| } | ||
|
|
||
| if (path.charAt(0) != '/') { | ||
| respondWithHttpError(ctx, streamId, 404, Status.Code.UNIMPLEMENTED, | ||
| String.format("Expected path to start with /: %s", path)); | ||
| return; | ||
| } | ||
|
|
||
| String method = path.subSequence(1, path.length()).toString(); | ||
|
|
||
| // Verify that the Content-Type is correct in the request. | ||
| verifyContentType(streamId, headers); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oops. I didn't delete these methods. They're now unused:
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I went ahead and deleted them, since it was trivial.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ejona86 great..thanks..I should have done it my self. Did not pay attention. Sorry :-) |
||
| String method = determineMethod(streamId, headers); | ||
| CharSequence contentType = headers.get(CONTENT_TYPE_HEADER); | ||
| if (contentType == null) { | ||
| respondWithHttpError( | ||
| ctx, streamId, 415, Status.Code.INTERNAL, "Content-Type is missing from the request"); | ||
| return; | ||
| } | ||
| String contentTypeString = contentType.toString(); | ||
| if (!GrpcUtil.isGrpcContentType(contentTypeString)) { | ||
| respondWithHttpError(ctx, streamId, 415, Status.Code.INTERNAL, | ||
| String.format("Content-Type '%s' is not supported", contentTypeString)); | ||
| return; | ||
| } | ||
|
|
||
| if (!HTTP_METHOD.equals(headers.method())) { | ||
| respondWithHttpError(ctx, streamId, 405, Status.Code.INTERNAL, | ||
| String.format("Method '%s' is not supported", headers.method())); | ||
| return; | ||
| } | ||
|
|
||
| // The Http2Stream object was put by AbstractHttp2ConnectionHandler before calling this | ||
| // method. | ||
|
|
@@ -400,7 +437,7 @@ private void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers | |
| maxMessageSize, | ||
| statsTraceCtx, | ||
| transportTracer); | ||
| String authority = getOrUpdateAuthority((AsciiString)headers.authority()); | ||
| String authority = getOrUpdateAuthority((AsciiString) headers.authority()); | ||
| NettyServerStream stream = new NettyServerStream( | ||
| ctx.channel(), | ||
| state, | ||
|
|
@@ -411,10 +448,7 @@ private void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers | |
| transportListener.streamCreated(stream, method, metadata); | ||
| state.onStreamAllocated(); | ||
| http2Stream.setProperty(streamKey, state); | ||
|
|
||
| } catch (Http2Exception e) { | ||
| throw e; | ||
| } catch (Throwable e) { | ||
| } catch (Exception e) { | ||
| logger.log(Level.WARNING, "Exception in onHeadersRead()", e); | ||
| // Throw an exception that will get handled by onStreamError. | ||
| throw newStreamException(streamId, e); | ||
|
|
@@ -634,17 +668,22 @@ public boolean visit(Http2Stream stream) throws Http2Exception { | |
| }); | ||
| } | ||
|
|
||
| private void verifyContentType(int streamId, Http2Headers headers) throws Http2Exception { | ||
| CharSequence contentType = headers.get(CONTENT_TYPE_HEADER); | ||
| if (contentType == null) { | ||
| throw Http2Exception.streamError(streamId, Http2Error.REFUSED_STREAM, | ||
| "Content-Type is missing from the request"); | ||
| } | ||
| String contentTypeString = contentType.toString(); | ||
| if (!GrpcUtil.isGrpcContentType(contentTypeString)) { | ||
| throw Http2Exception.streamError(streamId, Http2Error.REFUSED_STREAM, | ||
| "Content-Type '%s' is not supported", contentTypeString); | ||
| } | ||
| private void respondWithHttpError( | ||
| ChannelHandlerContext ctx, int streamId, int code, Status.Code statusCode, String msg) { | ||
| Metadata metadata = new Metadata(); | ||
| metadata.put(InternalStatus.CODE_KEY, statusCode.toStatus()); | ||
| metadata.put(InternalStatus.MESSAGE_KEY, msg); | ||
| byte[][] serialized = InternalMetadata.serialize(metadata); | ||
|
|
||
| Http2Headers headers = new DefaultHttp2Headers(true, serialized.length / 2) | ||
| .status("" + code) | ||
| .set(CONTENT_TYPE_HEADER, "text/plain; encoding=utf-8"); | ||
| for (int i = 0; i < serialized.length; i += 2) { | ||
| headers.add(new AsciiString(serialized[i], false), new AsciiString(serialized[i + 1], false)); | ||
| } | ||
| encoder().writeHeaders(ctx, streamId, headers, 0, false, ctx.newPromise()); | ||
| ByteBuf msgBuf = ByteBufUtil.writeUtf8(ctx.alloc(), msg); | ||
| encoder().writeData(ctx, streamId, msgBuf, 0, true, ctx.newPromise()); | ||
| } | ||
|
|
||
| private Http2Stream requireHttp2Stream(int streamId) { | ||
|
|
@@ -656,20 +695,6 @@ private Http2Stream requireHttp2Stream(int streamId) { | |
| return stream; | ||
| } | ||
|
|
||
| private String determineMethod(int streamId, Http2Headers headers) throws Http2Exception { | ||
| if (!HTTP_METHOD.equals(headers.method())) { | ||
| throw Http2Exception.streamError(streamId, Http2Error.REFUSED_STREAM, | ||
| "Method '%s' is not supported", headers.method()); | ||
| } | ||
| // Remove the leading slash of the path and get the fully qualified method name | ||
| CharSequence path = headers.path(); | ||
| if (path.charAt(0) != '/') { | ||
| throw Http2Exception.streamError(streamId, Http2Error.REFUSED_STREAM, | ||
| "Malformatted path: %s", path); | ||
| } | ||
| return path.subSequence(1, path.length()).toString(); | ||
| } | ||
|
|
||
| /** | ||
| * Returns the server stream associated to the given HTTP/2 stream object. | ||
| */ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
path.substring()There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pathis a CharSequence, so it doesn't havesubstring.