{"action":"created","comment":{"url":"https://api.github.com/repos/netty/netty/pulls/comments/28782906","id":28782906,"diff_hunk":"@@ -0,0 +1,319 @@\n+/*\n+ * Copyright 2015 The Netty Project\n+ *\n+ * The Netty Project licenses this file to you under the Apache License,\n+ * version 2.0 (the \"License\"); you may not use this file except in compliance\n+ * with the License. You may obtain a copy of the License at:\n+ *\n+ *   http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing, software\n+ * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n+ * License for the specific language governing permissions and limitations\n+ * under the License.\n+ */\n+package io.netty.handler.codec.http2;\n+\n+import static io.netty.handler.codec.http.HttpResponseStatus.OK;\n+import static io.netty.handler.logging.LogLevel.INFO;\n+\n+import io.netty.bootstrap.Bootstrap;\n+import io.netty.bootstrap.ServerBootstrap;\n+import io.netty.buffer.ByteBuf;\n+import io.netty.channel.Channel;\n+import io.netty.channel.ChannelHandler;\n+import io.netty.channel.ChannelHandlerAdapter;\n+import io.netty.channel.ChannelHandlerAppender;\n+import io.netty.channel.ChannelHandlerContext;\n+import io.netty.channel.ChannelFuture;\n+import io.netty.channel.EventLoop;\n+import io.netty.channel.EventLoopGroup;\n+import io.netty.handler.codec.http.FullHttpRequest;\n+import io.netty.handler.codec.http.HttpHeaders;\n+import io.netty.handler.codec.http.HttpServerUpgradeHandler.UpgradeEvent;\n+import io.netty.util.AsciiString;\n+\n+import java.util.ArrayList;\n+import java.util.List;\n+import java.util.Map;\n+\n+public class Http2MultiplexCodec extends ChannelHandlerAppender {\n+    private static final Http2FrameLogger logger\n+        = new Http2FrameLogger(INFO, Http2MultiplexCodec.class);\n+\n+    private final boolean server;\n+    private final ChannelHandler streamHandler;\n+    private final EventLoopGroup streamGroup;\n+    private final Http2ConnectionHandler http2Handler;\n+    private ChannelHandlerContext ctx;\n+    private final List<StreamInfo> streamsToFireChildReadComplete = new ArrayList<StreamInfo>();\n+\n+    /** {@code streamHandler} should be {@link ChannelHandler.Sharable}. */\n+    public Http2MultiplexCodec(boolean server, ChannelHandler streamHandler) {\n+        this(server, streamHandler, null);\n+    }\n+\n+    /** {@code streamHandler} should be {@link ChannelHandler.Sharable}. */\n+    public Http2MultiplexCodec(boolean server, ChannelHandler streamHandler,\n+            EventLoopGroup streamGroup) {\n+        super(false /* selfRemoval */);\n+        this.server = server;\n+        this.streamHandler = streamHandler;\n+        this.streamGroup = new io.netty.channel.DefaultEventLoopGroup(); //streamGroup;\n+        http2Handler = new Http2ConnectionHandler(new DefaultHttp2Connection(server),\n+                new Http2InboundFrameLogger(new DefaultHttp2FrameReader(), logger),\n+                new Http2OutboundFrameLogger(new DefaultHttp2FrameWriter(), logger),\n+                new FrameListener());\n+        http2Handler.connection().addListener(new ConnectionListener());\n+        add(http2Handler);\n+    }\n+\n+    Http2ConnectionHandler connectionHandler() {\n+        return http2Handler;\n+    }\n+\n+    @Override\n+    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {\n+        this.ctx = ctx;\n+        super.handlerAdded(ctx);\n+    }\n+\n+    /**\n+     * Handles the cleartext HTTP upgrade event. If an upgrade occurred, sends a simple response via\n+     * HTTP/2 on stream 1 (the stream specifically reserved for cleartext HTTP upgrade).\n+     */\n+    @Override\n+    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\n+        if (!(evt instanceof UpgradeEvent)) {\n+            super.userEventTriggered(ctx, evt);\n+        }\n+\n+        UpgradeEvent upgrade = (UpgradeEvent) evt;\n+        FullHttpRequest request = upgrade.upgradeRequest();\n+        Http2Stream stream = http2Handler.connection().requireStream(1);\n+        // The stream was already made active, but ctx may have been null so it wasn't initialized.\n+        new ConnectionListener().onStreamActive(stream);\n+        FrameListener listener = new FrameListener();\n+\n+        Http2Headers requestHeaders = HttpUtil.toHttp2Headers(request);\n+        boolean hasContent = request.content().readableBytes() > 0;\n+        boolean hasTrailers = !request.trailingHeaders().isEmpty();\n+        listener.onHeadersRead(ctx, stream.id(), requestHeaders, 0,\n+                !(hasContent && hasTrailers));\n+        if (hasContent) {\n+            listener.onDataRead(ctx, stream.id(), request.content(), 0,\n+                !hasTrailers);\n+        }\n+        if (hasTrailers) {\n+            Http2Headers headers = httpToHttp2Headers(request.trailingHeaders());\n+            listener.onHeadersRead(ctx, stream.id(), headers, 0, true);\n+        }\n+    }\n+\n+    private static Http2Headers httpToHttp2Headers(HttpHeaders http1) {\n+        Http2Headers http2 = new DefaultHttp2Headers();\n+        for (Map.Entry<CharSequence, CharSequence> me : http1) {\n+            http2.add(new AsciiString(me.getKey()), new AsciiString(me.getValue()));\n+        }\n+        return http2;\n+    }\n+\n+    Http2StreamChannel createStreamChannel(ChannelHandlerContext ctx, Http2Stream stream,\n+            ChannelHandler handler) {\n+        EventLoopGroup group = streamGroup != null ? streamGroup : ctx.channel().eventLoop();\n+        Http2StreamChannel channel = new Http2StreamChannel(stream);\n+        channel.pipeline().addLast(handler);\n+        group.register(channel);\n+        return channel;\n+    }\n+\n+    @Override\n+    public void channelReadComplete(ChannelHandlerContext ctx) {\n+        for (int i = 0; i < streamsToFireChildReadComplete.size(); i++) {\n+            final StreamInfo streamInfo = streamsToFireChildReadComplete.get(i);\n+            // Clear early in case fireChildReadComplete() causes it to need to be re-processed\n+            streamInfo.inStreamsToFireChildReadComplete = false;\n+            EventLoop childLoop = streamInfo.childChannel.eventLoop();\n+            if (childLoop.inEventLoop()) {\n+                streamInfo.childChannel.fireChildReadComplete();\n+            } else {\n+                childLoop.execute(new Runnable() {\n+                    @Override\n+                    public void run() {\n+                        streamInfo.childChannel.fireChildReadComplete();\n+                    }\n+                });\n+            }\n+        }\n+        streamsToFireChildReadComplete.clear();\n+    }\n+\n+    void fireChildReadAndRegister(StreamInfo streamInfo, Object msg) {\n+        streamInfo.childChannel.fireChildRead(msg);\n+        if (!streamInfo.inStreamsToFireChildReadComplete) {\n+            streamsToFireChildReadComplete.add(streamInfo);\n+            streamInfo.inStreamsToFireChildReadComplete = true;\n+        }\n+    }\n+\n+    class ConnectionListener extends Http2ConnectionAdapter {\n+        @Override\n+        public void onStreamActive(Http2Stream stream) {\n+            if (ctx == null) {\n+                // UPGRADE stream is active before handlerAdded().\n+                return;\n+            }\n+            // If it is an outgoing stream, then we already created the channel.\n+            // TODO: support outgoing streams\n+            if (stream.getProperty(StreamInfo.class) != null) {\n+                return;\n+            }\n+            stream.setProperty(StreamInfo.class,\n+                new StreamInfo(createStreamChannel(ctx, stream, streamHandler)));\n+        }\n+\n+        @Override\n+        public void onStreamClosed(Http2Stream stream) {\n+            StreamInfo streamInfo = (StreamInfo) stream.removeProperty(StreamInfo.class);\n+            if (streamInfo != null) {\n+                streamInfo.childChannel.close();\n+            }\n+        }\n+    }\n+\n+    class FrameListener extends Http2FrameAdapter {\n+        @Override\n+        public void onHeadersRead(ChannelHandlerContext ctx, int streamId,\n+                Http2Headers headers, int streamDependency, short weight, boolean\n+                exclusive, int padding, boolean endStream) throws Http2Exception {\n+            onHeadersRead(ctx, streamId, headers, padding, endStream);\n+        }\n+\n+        @Override\n+        public void onHeadersRead(ChannelHandlerContext ctx, int streamId,\n+                final Http2Headers headers, int padding, final boolean endOfStream)\n+                throws Http2Exception {\n+            Http2Stream stream = http2Handler.connection().requireStream(streamId);\n+            final StreamInfo streamInfo = (StreamInfo) stream.getProperty(StreamInfo.class);\n+            EventLoop childLoop = streamInfo.childChannel.eventLoop();\n+            if (childLoop.inEventLoop()) {\n+                childOnHeadersRead(streamInfo, headers, endOfStream);\n+            } else {\n+                childLoop.execute(new Runnable() {\n+                    @Override\n+                    public void run() {\n+                        childOnHeadersRead(streamInfo, headers, endOfStream);\n+                    }\n+                });\n+            }\n+        }\n+\n+        void childOnHeadersRead(StreamInfo streamInfo, Http2Headers headers, boolean endOfStream) {\n+            fireChildReadAndRegister(streamInfo, headers);\n+            if (endOfStream) {\n+                // TODO\n+            }\n+        }\n+\n+        @Override\n+        public int onDataRead(ChannelHandlerContext ctx, int streamId, final ByteBuf data,\n+                int padding, final boolean endOfStream) throws Http2Exception {\n+            Http2Stream stream = http2Handler.connection().requireStream(streamId);\n+            final StreamInfo streamInfo = (StreamInfo) stream.getProperty(StreamInfo.class);\n+            EventLoop childLoop = streamInfo.childChannel.eventLoop();\n+            if (childLoop.inEventLoop()) {\n+                childOnDataRead(streamInfo, data, endOfStream);\n+            } else {\n+                childLoop.execute(new Runnable() {\n+                    @Override\n+                    public void run() {\n+                        childOnDataRead(streamInfo, data, endOfStream);\n+                    }\n+                });\n+            }\n+            return data.readableBytes() + padding;\n+        }\n+\n+        void childOnDataRead(StreamInfo streamInfo, ByteBuf data, boolean endOfStream) {\n+            fireChildReadAndRegister(streamInfo, data);\n+            if (endOfStream) {\n+                // TODO\n+            }\n+        }\n+    }\n+\n+    static class StreamInfo {\n+        final Http2StreamChannel childChannel;\n+        /**\n+         * True if stream is in {@link Http2MultiplexCodec#steamsToFireChildReadComplete}.\n+         */\n+        boolean inStreamsToFireChildReadComplete;\n+\n+        StreamInfo(Http2StreamChannel childChannel) {\n+            this.childChannel = childChannel;\n+        }\n+    }\n+\n+    class Http2StreamChannel extends ChildChannel {\n+        private final Http2Stream stream;\n+\n+        Http2StreamChannel(Http2Stream stream) {\n+            super(Http2MultiplexCodec.this.ctx.channel());\n+            this.stream = stream;\n+        }\n+\n+        @Override\n+        public void doClose() throws Exception {\n+            EventLoop parentLoop = ctx.channel().eventLoop();\n+            if (parentLoop.inEventLoop()) {\n+                parentClose();\n+            } else {\n+                parentLoop.execute(new Runnable() {\n+                    @Override\n+                    public void run() {\n+                        parentClose();\n+                    }\n+                });\n+            }\n+            super.doClose();\n+        }\n+\n+        public void parentClose() {\n+            try {\n+                if (stream.state() != Http2Stream.State.CLOSED) {\n+                    http2Handler.resetStream(ctx, stream.id(), Http2Error.CANCEL.code(),\n+                        ctx.newPromise());\n+                }\n+            } catch (Throwable t) {\n+                parentExceptionCaught(t);\n+            }\n+        }\n+\n+        @Override\n+        public void parentWrite(Object msg) throws Exception {\n+            // TODO: fix endOfStream","path":"codec-http2/src/main/java/io/netty/handler/codec/http2/Http2MultiplexCodec.java","position":295,"original_position":295,"commit_id":"37a36038653aa984f0952e74bb515e7ff46809ed","original_commit_id":"37a36038653aa984f0952e74bb515e7ff46809ed","user":{"login":"nmittler","id":6493296,"avatar_url":"https://avatars.githubusercontent.com/u/6493296?v=3","gravatar_id":"","url":"https://api.github.com/users/nmittler","html_url":"https://github.com/nmittler","followers_url":"https://api.github.com/users/nmittler/followers","following_url":"https://api.github.com/users/nmittler/following{/other_user}","gists_url":"https://api.github.com/users/nmittler/gists{/gist_id}","starred_url":"https://api.github.com/users/nmittler/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nmittler/subscriptions","organizations_url":"https://api.github.com/users/nmittler/orgs","repos_url":"https://api.github.com/users/nmittler/repos","events_url":"https://api.github.com/users/nmittler/events{/privacy}","received_events_url":"https://api.github.com/users/nmittler/received_events","type":"User","site_admin":false},"body":"So what are the options...\r\n\r\n1) Create new objects to hold all of these goodies like `Http2HeadersFrame` and `Http2DataFrame`.\r\n\r\n2) Channel properties? Although there may be several messages queued for writing and it won't be clear which one has the endOfStream.\r\n\r\nAny other options?","created_at":"2015-04-21T14:29:58Z","updated_at":"2015-04-21T14:29:58Z","html_url":"https://github.com/netty/netty/pull/3666#discussion_r28782906","pull_request_url":"https://api.github.com/repos/netty/netty/pulls/3666","_links":{"self":{"href":"https://api.github.com/repos/netty/netty/pulls/comments/28782906"},"html":{"href":"https://github.com/netty/netty/pull/3666#discussion_r28782906"},"pull_request":{"href":"https://api.github.com/repos/netty/netty/pulls/3666"}}},"pull_request":{"url":"https://api.github.com/repos/netty/netty/pulls/3666","id":33687918,"html_url":"https://github.com/netty/netty/pull/3666","diff_url":"https://github.com/netty/netty/pull/3666.diff","patch_url":"https://github.com/netty/netty/pull/3666.patch","issue_url":"https://api.github.com/repos/netty/netty/issues/3666","number":3666,"state":"open","locked":false,"title":"POC: Map Streams to Channels","user":{"login":"ejona86","id":2811396,"avatar_url":"https://avatars.githubusercontent.com/u/2811396?v=3","gravatar_id":"","url":"https://api.github.com/users/ejona86","html_url":"https://github.com/ejona86","followers_url":"https://api.github.com/users/ejona86/followers","following_url":"https://api.github.com/users/ejona86/following{/other_user}","gists_url":"https://api.github.com/users/ejona86/gists{/gist_id}","starred_url":"https://api.github.com/users/ejona86/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ejona86/subscriptions","organizations_url":"https://api.github.com/users/ejona86/orgs","repos_url":"https://api.github.com/users/ejona86/repos","events_url":"https://api.github.com/users/ejona86/events{/privacy}","received_events_url":"https://api.github.com/users/ejona86/received_events","type":"User","site_admin":false},"body":"This allows using handlers for Streams in normal Netty-style.\r\n\r\nDetails can be discussed in this PR, but I do expect it to be thrown away or heavily-tweaked. The larger discussion should probably take place in an issue that will be created shortly.","created_at":"2015-04-20T17:34:50Z","updated_at":"2015-04-21T14:29:58Z","closed_at":null,"merged_at":null,"merge_commit_sha":"82207aa9c0fa6f83675eb456f346eddfc1439760","assignee":null,"milestone":{"url":"https://api.github.com/repos/netty/netty/milestones/112","html_url":"https://github.com/netty/netty/milestones/4.1.0.Beta5","labels_url":"https://api.github.com/repos/netty/netty/milestones/112/labels","id":1005380,"number":112,"title":"4.1.0.Beta5","description":"","creator":{"login":"normanmaurer","id":439362,"avatar_url":"https://avatars.githubusercontent.com/u/439362?v=3","gravatar_id":"","url":"https://api.github.com/users/normanmaurer","html_url":"https://github.com/normanmaurer","followers_url":"https://api.github.com/users/normanmaurer/followers","following_url":"https://api.github.com/users/normanmaurer/following{/other_user}","gists_url":"https://api.github.com/users/normanmaurer/gists{/gist_id}","starred_url":"https://api.github.com/users/normanmaurer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/normanmaurer/subscriptions","organizations_url":"https://api.github.com/users/normanmaurer/orgs","repos_url":"https://api.github.com/users/normanmaurer/repos","events_url":"https://api.github.com/users/normanmaurer/events{/privacy}","received_events_url":"https://api.github.com/users/normanmaurer/received_events","type":"User","site_admin":false},"open_issues":73,"closed_issues":76,"state":"open","created_at":"2015-03-04T09:22:23Z","updated_at":"2015-04-21T03:19:24Z","due_on":null,"closed_at":null},"commits_url":"https://api.github.com/repos/netty/netty/pulls/3666/commits","review_comments_url":"https://api.github.com/repos/netty/netty/pulls/3666/comments","review_comment_url":"https://api.github.com/repos/netty/netty/pulls/comments{/number}","comments_url":"https://api.github.com/repos/netty/netty/issues/3666/comments","statuses_url":"https://api.github.com/repos/netty/netty/statuses/37a36038653aa984f0952e74bb515e7ff46809ed","head":{"label":"ejona86:http2-stream-as-channel","ref":"http2-stream-as-channel","sha":"37a36038653aa984f0952e74bb515e7ff46809ed","user":{"login":"ejona86","id":2811396,"avatar_url":"https://avatars.githubusercontent.com/u/2811396?v=3","gravatar_id":"","url":"https://api.github.com/users/ejona86","html_url":"https://github.com/ejona86","followers_url":"https://api.github.com/users/ejona86/followers","following_url":"https://api.github.com/users/ejona86/following{/other_user}","gists_url":"https://api.github.com/users/ejona86/gists{/gist_id}","starred_url":"https://api.github.com/users/ejona86/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ejona86/subscriptions","organizations_url":"https://api.github.com/users/ejona86/orgs","repos_url":"https://api.github.com/users/ejona86/repos","events_url":"https://api.github.com/users/ejona86/events{/privacy}","received_events_url":"https://api.github.com/users/ejona86/received_events","type":"User","site_admin":false},"repo":{"id":32953128,"name":"netty","full_name":"ejona86/netty","owner":{"login":"ejona86","id":2811396,"avatar_url":"https://avatars.githubusercontent.com/u/2811396?v=3","gravatar_id":"","url":"https://api.github.com/users/ejona86","html_url":"https://github.com/ejona86","followers_url":"https://api.github.com/users/ejona86/followers","following_url":"https://api.github.com/users/ejona86/following{/other_user}","gists_url":"https://api.github.com/users/ejona86/gists{/gist_id}","starred_url":"https://api.github.com/users/ejona86/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ejona86/subscriptions","organizations_url":"https://api.github.com/users/ejona86/orgs","repos_url":"https://api.github.com/users/ejona86/repos","events_url":"https://api.github.com/users/ejona86/events{/privacy}","received_events_url":"https://api.github.com/users/ejona86/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/ejona86/netty","description":"Netty project - an event-driven asynchronous network application framework","fork":true,"url":"https://api.github.com/repos/ejona86/netty","forks_url":"https://api.github.com/repos/ejona86/netty/forks","keys_url":"https://api.github.com/repos/ejona86/netty/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ejona86/netty/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ejona86/netty/teams","hooks_url":"https://api.github.com/repos/ejona86/netty/hooks","issue_events_url":"https://api.github.com/repos/ejona86/netty/issues/events{/number}","events_url":"https://api.github.com/repos/ejona86/netty/events","assignees_url":"https://api.github.com/repos/ejona86/netty/assignees{/user}","branches_url":"https://api.github.com/repos/ejona86/netty/branches{/branch}","tags_url":"https://api.github.com/repos/ejona86/netty/tags","blobs_url":"https://api.github.com/repos/ejona86/netty/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ejona86/netty/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ejona86/netty/git/refs{/sha}","trees_url":"https://api.github.com/repos/ejona86/netty/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ejona86/netty/statuses/{sha}","languages_url":"https://api.github.com/repos/ejona86/netty/languages","stargazers_url":"https://api.github.com/repos/ejona86/netty/stargazers","contributors_url":"https://api.github.com/repos/ejona86/netty/contributors","subscribers_url":"https://api.github.com/repos/ejona86/netty/subscribers","subscription_url":"https://api.github.com/repos/ejona86/netty/subscription","commits_url":"https://api.github.com/repos/ejona86/netty/commits{/sha}","git_commits_url":"https://api.github.com/repos/ejona86/netty/git/commits{/sha}","comments_url":"https://api.github.com/repos/ejona86/netty/comments{/number}","issue_comment_url":"https://api.github.com/repos/ejona86/netty/issues/comments{/number}","contents_url":"https://api.github.com/repos/ejona86/netty/contents/{+path}","compare_url":"https://api.github.com/repos/ejona86/netty/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ejona86/netty/merges","archive_url":"https://api.github.com/repos/ejona86/netty/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ejona86/netty/downloads","issues_url":"https://api.github.com/repos/ejona86/netty/issues{/number}","pulls_url":"https://api.github.com/repos/ejona86/netty/pulls{/number}","milestones_url":"https://api.github.com/repos/ejona86/netty/milestones{/number}","notifications_url":"https://api.github.com/repos/ejona86/netty/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ejona86/netty/labels{/name}","releases_url":"https://api.github.com/repos/ejona86/netty/releases{/id}","created_at":"2015-03-26T21:34:39Z","updated_at":"2015-04-20T17:33:01Z","pushed_at":"2015-04-20T23:39:42Z","git_url":"git://github.com/ejona86/netty.git","ssh_url":"git@github.com:ejona86/netty.git","clone_url":"https://github.com/ejona86/netty.git","svn_url":"https://github.com/ejona86/netty","homepage":"http://netty.io/","size":41460,"stargazers_count":1,"watchers_count":1,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":1,"default_branch":"master"}},"base":{"label":"netty:master","ref":"master","sha":"43d57ddd46f0cf5db92469219654c155bd982e05","user":{"login":"netty","id":473791,"avatar_url":"https://avatars.githubusercontent.com/u/473791?v=3","gravatar_id":"","url":"https://api.github.com/users/netty","html_url":"https://github.com/netty","followers_url":"https://api.github.com/users/netty/followers","following_url":"https://api.github.com/users/netty/following{/other_user}","gists_url":"https://api.github.com/users/netty/gists{/gist_id}","starred_url":"https://api.github.com/users/netty/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/netty/subscriptions","organizations_url":"https://api.github.com/users/netty/orgs","repos_url":"https://api.github.com/users/netty/repos","events_url":"https://api.github.com/users/netty/events{/privacy}","received_events_url":"https://api.github.com/users/netty/received_events","type":"Organization","site_admin":false},"repo":{"id":1064563,"name":"netty","full_name":"netty/netty","owner":{"login":"netty","id":473791,"avatar_url":"https://avatars.githubusercontent.com/u/473791?v=3","gravatar_id":"","url":"https://api.github.com/users/netty","html_url":"https://github.com/netty","followers_url":"https://api.github.com/users/netty/followers","following_url":"https://api.github.com/users/netty/following{/other_user}","gists_url":"https://api.github.com/users/netty/gists{/gist_id}","starred_url":"https://api.github.com/users/netty/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/netty/subscriptions","organizations_url":"https://api.github.com/users/netty/orgs","repos_url":"https://api.github.com/users/netty/repos","events_url":"https://api.github.com/users/netty/events{/privacy}","received_events_url":"https://api.github.com/users/netty/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/netty/netty","description":"Netty project - an event-driven asynchronous network application framework","fork":false,"url":"https://api.github.com/repos/netty/netty","forks_url":"https://api.github.com/repos/netty/netty/forks","keys_url":"https://api.github.com/repos/netty/netty/keys{/key_id}","collaborators_url":"https://api.github.com/repos/netty/netty/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/netty/netty/teams","hooks_url":"https://api.github.com/repos/netty/netty/hooks","issue_events_url":"https://api.github.com/repos/netty/netty/issues/events{/number}","events_url":"https://api.github.com/repos/netty/netty/events","assignees_url":"https://api.github.com/repos/netty/netty/assignees{/user}","branches_url":"https://api.github.com/repos/netty/netty/branches{/branch}","tags_url":"https://api.github.com/repos/netty/netty/tags","blobs_url":"https://api.github.com/repos/netty/netty/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/netty/netty/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/netty/netty/git/refs{/sha}","trees_url":"https://api.github.com/repos/netty/netty/git/trees{/sha}","statuses_url":"https://api.github.com/repos/netty/netty/statuses/{sha}","languages_url":"https://api.github.com/repos/netty/netty/languages","stargazers_url":"https://api.github.com/repos/netty/netty/stargazers","contributors_url":"https://api.github.com/repos/netty/netty/contributors","subscribers_url":"https://api.github.com/repos/netty/netty/subscribers","subscription_url":"https://api.github.com/repos/netty/netty/subscription","commits_url":"https://api.github.com/repos/netty/netty/commits{/sha}","git_commits_url":"https://api.github.com/repos/netty/netty/git/commits{/sha}","comments_url":"https://api.github.com/repos/netty/netty/comments{/number}","issue_comment_url":"https://api.github.com/repos/netty/netty/issues/comments{/number}","contents_url":"https://api.github.com/repos/netty/netty/contents/{+path}","compare_url":"https://api.github.com/repos/netty/netty/compare/{base}...{head}","merges_url":"https://api.github.com/repos/netty/netty/merges","archive_url":"https://api.github.com/repos/netty/netty/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/netty/netty/downloads","issues_url":"https://api.github.com/repos/netty/netty/issues{/number}","pulls_url":"https://api.github.com/repos/netty/netty/pulls{/number}","milestones_url":"https://api.github.com/repos/netty/netty/milestones{/number}","notifications_url":"https://api.github.com/repos/netty/netty/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/netty/netty/labels{/name}","releases_url":"https://api.github.com/repos/netty/netty/releases{/id}","created_at":"2010-11-09T09:22:21Z","updated_at":"2015-04-21T13:08:49Z","pushed_at":"2015-04-21T13:12:22Z","git_url":"git://github.com/netty/netty.git","ssh_url":"git@github.com:netty/netty.git","clone_url":"https://github.com/netty/netty.git","svn_url":"https://github.com/netty/netty","homepage":"http://netty.io/","size":117820,"stargazers_count":4464,"watchers_count":4464,"language":"Java","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":2199,"mirror_url":null,"open_issues_count":262,"forks":2199,"open_issues":262,"watchers":4464,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/netty/netty/pulls/3666"},"html":{"href":"https://github.com/netty/netty/pull/3666"},"issue":{"href":"https://api.github.com/repos/netty/netty/issues/3666"},"comments":{"href":"https://api.github.com/repos/netty/netty/issues/3666/comments"},"review_comments":{"href":"https://api.github.com/repos/netty/netty/pulls/3666/comments"},"review_comment":{"href":"https://api.github.com/repos/netty/netty/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/netty/netty/pulls/3666/commits"},"statuses":{"href":"https://api.github.com/repos/netty/netty/statuses/37a36038653aa984f0952e74bb515e7ff46809ed"}}}}