{"action":"created","comment":{"url":"https://api.github.com/repos/netty/netty/pulls/comments/130369463","pull_request_review_id":53236258,"id":130369463,"diff_hunk":"@@ -284,194 +345,485 @@ private void writeFromStreamChannel0(Object msg, boolean flush, ChannelPromise p\n      */\n     @Override\n     public void channelReadComplete(ChannelHandlerContext ctx) {\n-        for (int i = 0; i < channelsToFireChildReadComplete.size(); i++) {\n-            Http2StreamChannel childChannel = channelsToFireChildReadComplete.get(i);\n-            // Clear early in case fireChildReadComplete() causes it to need to be re-processed\n-            childChannel.inStreamsToFireChildReadComplete = false;\n-            childChannel.fireChildReadComplete();\n-        }\n-        channelsToFireChildReadComplete.clear();\n-    }\n-\n-    ChannelFuture createStreamChannel(Channel parentChannel, EventLoopGroup group, ChannelHandler handler,\n-                                              Map<ChannelOption<?>, Object> options,\n-                                              Map<AttributeKey<?>, Object> attrs,\n-                                              int streamId) {\n-        final Http2StreamChannel channel = new Http2StreamChannel(parentChannel);\n-        if (isStreamIdValid(streamId)) {\n-            assert !isOutboundStream(server, streamId);\n-            assert ctx.channel().eventLoop().inEventLoop();\n-            channel.streamId(streamId);\n-        }\n-        channel.pipeline().addLast(handler);\n-\n-        initOpts(channel, options);\n-        initAttrs(channel, attrs);\n-\n-        ChannelFuture future = group.register(channel);\n-        // Handle any errors that occurred on the local thread while registering. Even though\n-        // failures can happen after this point, they will be handled by the channel by closing the\n-        // channel.\n-        if (future.cause() != null) {\n-            if (channel.isRegistered()) {\n-                channel.close();\n-            } else {\n-                channel.unsafe().closeForcibly();\n-            }\n-        }\n-        return future;\n-    }\n-\n-    @SuppressWarnings(\"unchecked\")\n-    private static void initOpts(Channel channel, Map<ChannelOption<?>, Object> opts) {\n-        if (opts != null) {\n-            for (Entry<ChannelOption<?>, Object> e: opts.entrySet()) {\n-                try {\n-                    if (!channel.config().setOption((ChannelOption<Object>) e.getKey(), e.getValue())) {\n-                        logger.warn(\"Unknown channel option: \" + e);\n+        // If we have many child channel we can optimize for the case when multiple call flush() in\n+        // channelReadComplete(...) callbacks and only do it once as otherwise we will end-up with multiple\n+        // write calls on the socket which is expensive.\n+        try {\n+            for (int i = 0; i < channelsToFireChildReadComplete.size(); i++) {\n+                DefaultHttp2StreamChannel childChannel = channelsToFireChildReadComplete.get(i);\n+                if (childChannel.inStreamsToFireChildReadComplete) {\n+                    // Clear early in case fireChildReadComplete() causes it to need to be re-processed\n+                    childChannel.inStreamsToFireChildReadComplete = false;\n+                    if (childChannel.fireChildReadComplete()) {\n+                        flushNeeded = true;\n                     }\n-                } catch (Throwable t) {\n-                    logger.warn(\"Failed to set a channel option: \" + channel, t);\n                 }\n             }\n-        }\n-    }\n+        } finally {\n+            channelsToFireChildReadComplete.clear();\n \n-    @SuppressWarnings(\"unchecked\")\n-    private static void initAttrs(Channel channel, Map<AttributeKey<?>, Object> attrs) {\n-        if (attrs != null) {\n-            for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {\n-                channel.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());\n+            if (flushNeeded) {\n+                flushNeeded = false;\n+                ctx.flush();\n             }\n         }\n     }\n \n-    final class Http2StreamChannel extends AbstractHttp2StreamChannel implements ChannelFutureListener {\n-        boolean onStreamClosedFired;\n+    private final class DefaultHttp2StreamChannel extends AbstractChannel implements Http2StreamChannel {\n+\n+        private final Http2StreamChannelConfig config = new Http2StreamChannelConfig(this);\n+        private final Queue<Object> inboundBuffer = new ArrayDeque<Object>(4);\n+        private final Http2FrameStream stream;\n+\n+        private boolean closed;\n+        private boolean readInProgress;\n+        private MessageSizeEstimator.Handle sizeEstimatorHandle;\n+\n+        /** {@code true} after the first HEADERS frame has been written **/\n+        private boolean firstFrameWritten;\n+\n+        /** {@code true} if a close without an error was initiated **/\n+        private boolean streamClosedWithoutError;\n+\n+        /** {@code true} if stream is in {@link Http2MultiplexCodec#channelsToFireChildReadComplete}. **/\n+        boolean inStreamsToFireChildReadComplete;\n+\n+        // Keeps track of flush calls in channelReadComplete(...) and aggregate these.\n+        private boolean inFireChannelReadComplete;\n+        private boolean flushPending;\n \n         /**\n-         * {@code true} if stream is in {@link Http2MultiplexCodec#channelsToFireChildReadComplete}.\n+         * The flow control window of the remote side i.e. the number of bytes this channel is allowed to send to the\n+         * remote peer. The window can become negative if a channel handler ignores the channel's writability. We are\n+         * using a long so that we realistically don't have to worry about underflow.\n          */\n-        boolean inStreamsToFireChildReadComplete;\n+        @SuppressWarnings(\"UnusedDeclaration\")\n+        volatile long outboundFlowControlWindow;\n \n-        Http2StreamChannel(Channel parentChannel) {\n-            super(parentChannel);\n+        DefaultHttp2StreamChannel(Http2FrameStream stream) {\n+            super(ctx.channel());\n+            this.stream = stream;\n         }\n \n         @Override\n-        protected void doClose() throws Exception {\n-            if (!onStreamClosedFired && isStreamIdValid(streamId())) {\n-                Http2StreamFrame resetFrame = new DefaultHttp2ResetFrame(Http2Error.CANCEL).streamId(streamId());\n-                writeFromStreamChannel(resetFrame, true);\n-            }\n-            super.doClose();\n+        public Http2FrameStream stream() {\n+            return stream;\n+        }\n+\n+        void streamClosed() {\n+            streamClosedWithoutError = true;\n+            fireChildRead(CLOSE_MESSAGE);\n         }\n \n         @Override\n-        protected void doWrite(Object msg) {\n-\n-            if (msg instanceof Http2StreamFrame) {\n-                Http2StreamFrame frame = (Http2StreamFrame) msg;\n-                ChannelPromise promise = ctx.newPromise();\n-                if (isStreamIdValid(frame.streamId())) {\n-                    ReferenceCountUtil.release(frame);\n-                    throw new IllegalArgumentException(\"Stream id must not be set on the frame. Was: \"\n-                        + frame.streamId());\n-                }\n-                if (!isStreamIdValid(streamId())) {\n-                    if (!(frame instanceof Http2HeadersFrame)) {\n-                        ReferenceCountUtil.release(frame);\n-                        throw new IllegalArgumentException(\"The first frame must be a headers frame. Was: \"\n-                            + frame.name());\n-                    }\n-                    frame = new ChannelCarryingHeadersFrame((Http2HeadersFrame) frame, this);\n-                    // Handle errors on stream creation\n-                    promise.addListener(this);\n-                } else {\n-                    frame.streamId(streamId());\n-                }\n-                writeFromStreamChannel(frame, promise, false);\n-            } else if (msg instanceof Http2GoAwayFrame) {\n-                ChannelPromise promise = ctx.newPromise();\n-                promise.addListener(this);\n-                writeFromStreamChannel(msg, promise, false);\n-            } else {\n-                ReferenceCountUtil.release(msg);\n-                throw new IllegalArgumentException(\"Message must be an Http2GoAwayFrame or Http2StreamFrame: \" + msg);\n-            }\n+        public ChannelMetadata metadata() {\n+            return METADATA;\n         }\n \n         @Override\n-        protected void doWriteComplete() {\n-            flushFromStreamChannel();\n+        public ChannelConfig config() {\n+            return config;\n         }\n \n         @Override\n-        protected EventExecutor preferredEventExecutor() {\n-            return ctx.executor();\n+        public boolean isOpen() {\n+            return !closed;\n         }\n \n         @Override\n-        protected void bytesConsumed(final int bytes) {\n-            ctx.write(new DefaultHttp2WindowUpdateFrame(bytes).streamId(streamId()));\n+        public boolean isActive() {\n+            return isOpen();\n         }\n \n         @Override\n-        public void operationComplete(ChannelFuture future) throws Exception {\n-            Throwable cause = future.cause();\n-            if (cause != null) {\n-                pipeline().fireExceptionCaught(cause);\n-                close();\n-            }\n+        public boolean isWritable() {\n+            return isStreamIdValid(stream.id())\n+                    // So that the channel doesn't become active before the initial flow control window has been set.\n+                    && outboundFlowControlWindow > 0\n+                    // Could be null if channel closed.\n+                    && unsafe().outboundBuffer() != null\n+                    && unsafe().outboundBuffer().isWritable();\n         }\n-    }\n \n-    /**\n-     * Wraps the first {@link Http2HeadersFrame} of local/outbound stream. This allows us to get to the child channel\n-     * when receiving the {@link Http2StreamActiveEvent} from the frame codec. See {@link #onStreamActive}.\n-     */\n-    private static final class ChannelCarryingHeadersFrame implements Http2HeadersFrame {\n+        @Override\n+        protected AbstractUnsafe newUnsafe() {\n+            return new Http2ChannelUnsafe();\n+        }\n \n-        private final Http2HeadersFrame frame;\n-        private final Http2StreamChannel childChannel;\n+        @Override\n+        protected boolean isCompatible(EventLoop loop) {\n+            return loop == parent().eventLoop();\n+        }\n \n-        ChannelCarryingHeadersFrame(Http2HeadersFrame frame, Http2StreamChannel childChannel) {\n-            this.frame = frame;\n-            this.childChannel = childChannel;\n+        @Override\n+        protected SocketAddress localAddress0() {\n+            return parent().localAddress();\n         }\n \n         @Override\n-        public Http2Headers headers() {\n-            return frame.headers();\n+        protected SocketAddress remoteAddress0() {\n+            return parent().remoteAddress();\n         }\n \n         @Override\n-        public boolean isEndStream() {\n-            return frame.isEndStream();\n+        protected void doBind(SocketAddress localAddress) throws Exception {\n+            throw new UnsupportedOperationException();\n         }\n \n         @Override\n-        public int padding() {\n-            return frame.padding();\n+        protected void doDisconnect() throws Exception {\n+            doClose();\n         }\n \n         @Override\n-        public Http2StreamFrame streamId(int streamId) {\n-            return frame.streamId(streamId);\n+        protected void doClose() throws Exception {\n+            closed = true;\n+\n+            // Only ever send a reset frame if the connection is still alove as otherwise it makes no sense at all\n+            // anyway.\n+            if (ctx.channel().isActive() && !streamClosedWithoutError && isStreamIdValid(stream().id())) {\n+                Http2StreamFrame resetFrame = new DefaultHttp2ResetFrame(Http2Error.CANCEL).stream(stream());\n+                ctx.write(resetFrame);\n+                mayFlush();\n+            }\n+\n+            while (!inboundBuffer.isEmpty()) {\n+                ReferenceCountUtil.release(inboundBuffer.poll());\n+            }\n         }\n \n         @Override\n-        public int streamId() {\n-            return frame.streamId();\n+        protected void doBeginRead() {\n+            if (readInProgress) {\n+                return;\n+            }\n+\n+            final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();\n+            allocHandle.reset(config());\n+            if (inboundBuffer.isEmpty()) {\n+                readInProgress = true;\n+                return;\n+            }\n+\n+            do {\n+                Object m = inboundBuffer.poll();\n+                if (m == null) {\n+                    break;\n+                }\n+                if (!doRead0(m, allocHandle)) {\n+                    // Channel closed, and already cleaned up.\n+                    return;\n+                }\n+            } while (allocHandle.continueReading());\n+\n+            allocHandle.readComplete();\n+            pipeline().fireChannelReadComplete();\n+        }\n+\n+        void setWritable() {\n+            assert !isWritable();\n+            incrementOutboundFlowControlWindow(initialOutboundStreamWindow);\n+            pipeline().fireChannelWritabilityChanged();\n         }\n \n         @Override\n-        public String name() {\n-            return frame.name();\n+        protected void doWrite(ChannelOutboundBuffer in) throws Exception {\n+            if (closed) {\n+                throw CLOSED_CHANNEL_EXCEPTION;\n+            }\n+\n+            try {\n+                if (sizeEstimatorHandle == null) {\n+                    sizeEstimatorHandle = config().getMessageSizeEstimator().newHandle();\n+                }\n+                for (;;) {\n+                    final Object msg = in.current();\n+                    if (msg == null) {\n+                        break;\n+                    }\n+                    if (msg == REEVALUATE_WRITABILITY_MESSAGE) {\n+                        in.remove();\n+                        continue;\n+                    }\n+\n+                    final int bytes = sizeEstimatorHandle.size(msg);\n+\n+                    /*\n+                     * The flow control window needs to be decrement before stealing the message from the buffer (and\n+                     * thereby decrementing the number of pending bytes). Else, when calling steal() the number of\n+                     * pending bytes could  be less than the writebuffer watermark (=flow control window) and thus\n+                     * trigger a  writability change.\n+                     *\n+                     * This code must never trigger a writability change. Only reading window updates or channel writes\n+                      * may change the channel's writability.\n+                     */\n+                    incrementOutboundFlowControlWindow(-bytes);\n+\n+                    final ChannelPromise promise = in.steal();\n+\n+                    if (promise.isCancelled()) {\n+                        // Write was cancelled so skip it.\n+                        continue;\n+                    }\n+","path":"codec-http2/src/main/java/io/netty/handler/codec/http2/Http2MultiplexCodec.java","position":840,"original_position":840,"commit_id":"4000cb9c9a47d2581c2dfd0fe7f770e3e2a268ab","original_commit_id":"4000cb9c9a47d2581c2dfd0fe7f770e3e2a268ab","user":{"login":"netkins","id":9507557,"avatar_url":"https://avatars3.githubusercontent.com/u/9507557?v=4","gravatar_id":"","url":"https://api.github.com/users/netkins","html_url":"https://github.com/netkins","followers_url":"https://api.github.com/users/netkins/followers","following_url":"https://api.github.com/users/netkins/following{/other_user}","gists_url":"https://api.github.com/users/netkins/gists{/gist_id}","starred_url":"https://api.github.com/users/netkins/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/netkins/subscriptions","organizations_url":"https://api.github.com/users/netkins/orgs","repos_url":"https://api.github.com/users/netkins/repos","events_url":"https://api.github.com/users/netkins/events{/privacy}","received_events_url":"https://api.github.com/users/netkins/received_events","type":"User","site_admin":false},"body":"![INFO](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/severity-info.png) Complete the task associated to this TODO comment. [![rule](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/rule.png)](https://garage.netty.io/sonarqube/coding_rules#rule_key=squid%3AS1135)\n","created_at":"2017-07-31T14:32:57Z","updated_at":"2017-07-31T14:32:57Z","html_url":"https://github.com/netty/netty/pull/6896#discussion_r130369463","pull_request_url":"https://api.github.com/repos/netty/netty/pulls/6896","_links":{"self":{"href":"https://api.github.com/repos/netty/netty/pulls/comments/130369463"},"html":{"href":"https://github.com/netty/netty/pull/6896#discussion_r130369463"},"pull_request":{"href":"https://api.github.com/repos/netty/netty/pulls/6896"}}},"pull_request":{"url":"https://api.github.com/repos/netty/netty/pulls/6896","id":127189800,"html_url":"https://github.com/netty/netty/pull/6896","diff_url":"https://github.com/netty/netty/pull/6896.diff","patch_url":"https://github.com/netty/netty/pull/6896.patch","issue_url":"https://api.github.com/repos/netty/netty/issues/6896","number":6896,"state":"open","locked":false,"title":"Http2 child","user":{"login":"normanmaurer","id":439362,"avatar_url":"https://avatars3.githubusercontent.com/u/439362?v=4","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},"body":"Rebased https://github.com/netty/netty/pull/5914 and pushed in a new branch so we can finish this :)\r\n\r\nSorry @buchgr that it took so long...","created_at":"2017-06-23T11:43:10Z","updated_at":"2017-07-31T14:32:58Z","closed_at":null,"merged_at":null,"merge_commit_sha":"1106171b57ffaa79071e9a8084564bc27a0301db","assignee":null,"assignees":[],"requested_reviewers":[],"milestone":null,"commits_url":"https://api.github.com/repos/netty/netty/pulls/6896/commits","review_comments_url":"https://api.github.com/repos/netty/netty/pulls/6896/comments","review_comment_url":"https://api.github.com/repos/netty/netty/pulls/comments{/number}","comments_url":"https://api.github.com/repos/netty/netty/issues/6896/comments","statuses_url":"https://api.github.com/repos/netty/netty/statuses/4000cb9c9a47d2581c2dfd0fe7f770e3e2a268ab","head":{"label":"netty:http2_child","ref":"http2_child","sha":"4000cb9c9a47d2581c2dfd0fe7f770e3e2a268ab","user":{"login":"netty","id":473791,"avatar_url":"https://avatars1.githubusercontent.com/u/473791?v=4","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://avatars1.githubusercontent.com/u/473791?v=4","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}","deployments_url":"https://api.github.com/repos/netty/netty/deployments","created_at":"2010-11-09T09:22:21Z","updated_at":"2017-07-31T12:04:05Z","pushed_at":"2017-07-31T13:02:42Z","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":48577,"stargazers_count":10750,"watchers_count":10750,"language":"Java","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":5168,"mirror_url":null,"open_issues_count":374,"forks":5168,"open_issues":374,"watchers":10750,"default_branch":"4.1"}},"base":{"label":"netty:4.1","ref":"4.1","sha":"21b7ab1f25f8c7032d74a82305bc12f18f863faf","user":{"login":"netty","id":473791,"avatar_url":"https://avatars1.githubusercontent.com/u/473791?v=4","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://avatars1.githubusercontent.com/u/473791?v=4","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}","deployments_url":"https://api.github.com/repos/netty/netty/deployments","created_at":"2010-11-09T09:22:21Z","updated_at":"2017-07-31T12:04:05Z","pushed_at":"2017-07-31T13:02:42Z","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":48577,"stargazers_count":10750,"watchers_count":10750,"language":"Java","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":5168,"mirror_url":null,"open_issues_count":374,"forks":5168,"open_issues":374,"watchers":10750,"default_branch":"4.1"}},"_links":{"self":{"href":"https://api.github.com/repos/netty/netty/pulls/6896"},"html":{"href":"https://github.com/netty/netty/pull/6896"},"issue":{"href":"https://api.github.com/repos/netty/netty/issues/6896"},"comments":{"href":"https://api.github.com/repos/netty/netty/issues/6896/comments"},"review_comments":{"href":"https://api.github.com/repos/netty/netty/pulls/6896/comments"},"review_comment":{"href":"https://api.github.com/repos/netty/netty/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/netty/netty/pulls/6896/commits"},"statuses":{"href":"https://api.github.com/repos/netty/netty/statuses/4000cb9c9a47d2581c2dfd0fe7f770e3e2a268ab"}}}}
{"actor":{"display_login":"netkins"}}