{"action":"created","comment":{"url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/pulls/comments/33329543","id":33329543,"diff_hunk":"@@ -1,110 +1,221 @@\n package com.typesafe.netty.http.pipelining;\n \n-import org.jboss.netty.channel.*;\n-import org.jboss.netty.handler.codec.http.HttpRequest;\n+\n+import io.netty.channel.*;\n+import io.netty.handler.codec.http.*;\n+import io.netty.util.collection.IntObjectHashMap;\n+import io.netty.util.collection.IntObjectMap;\n \n import java.util.*;\n+import java.util.concurrent.atomic.AtomicInteger;\n \n /**\n  * Implements HTTP pipelining ordering, ensuring that responses are completely served in the same order as their\n- * corresponding requests. NOTE: A side effect of using this handler is that upstream HttpRequest objects will\n- * cause the original message event to be effectively transformed into an OrderedUpstreamMessageEvent. Conversely\n- * OrderedDownstreamChannelEvent objects are expected to be received for the correlating response objects.\n+ * corresponding requests.\n+ *\n+ * Each incoming request is assigned a sequence number, which is provided in the wrapping {@link SequencedHttpRequest}.\n+ * When a handler writes a response and a response body, it must wrap each of those messages in a\n+ * {@link SequencedOutboundMessage} with the corresponding sequence from that request.  It must send at least one\n+ * message in response to the request, and the last, and only the last message it sends must have the last flag set.\n+ *\n+ * If messages are sent after the last message is sent, these messages may end up being buffered until the sequence\n+ * number overflows and cycles back. If messages for a given sequence number are not sent sequentially, similar\n+ * behaviour could also occur.\n+ *\n+ * There is no limit to the amount of messages that this handler will buffer for a particular sequence number.  It is\n+ * the responsibility of the handler sending the outbound messages to handle back pressure via promises associated\n+ * with each write event - if this is done, the buffering will be inherently bounded by back pressure.\n  *\n- * @author Christopher Hunt\n+ * This handler does however put a bound on the maximum number of in flight requests that it will handle, configured by\n+ * inFlightRequestsLowWatermark and inFlightRequestsHighWatermark. When the high watermark is exceeded, the handler will\n+ * push back on the client. When the low watermark is reached, the handler will start reading again.  This back pressure\n+ * mechanism only works if ChannelOptions.AUTO_READ is false.\n+ *\n+ * This back pressure is implemented by blocking channelReadComplete events, so assumes that the following handlers\n+ * will not request reading unless they receive this event.  Note that the handler does nothing to actually block\n+ * incoming requests when the high watermark is reached, it only pushes back on the TCP connection.  If there are more\n+ * requests in the TCP buffers before this back pressure takes effect, including the current request that triggered the\n+ * high water mark to be exceeded, these requests will still be sent to the following handlers.\n+ *\n+ * When the low watermark is reached after the high watermark has been exceeded, the handler will stop blocking\n+ * channelReadComplete events, and will issue a read.\n  */\n-public class HttpPipeliningHandler extends SimpleChannelHandler {\n+public class HttpPipeliningHandler extends ChannelDuplexHandler {\n \n-    public static final int INITIAL_EVENTS_HELD = 3;\n-    public static final int MAX_EVENTS_HELD = 10000;\n+    /**\n+     * The sequence of received HTTP requests.\n+     */\n+    volatile private int receiveSequence = 0;\n \n-    private final int maxEventsHeld;\n+    /**\n+     * The currently sending sequence of HTTP requests.\n+     */\n+    volatile private int currentlySendingSequence = 1;\n \n-    private int sequence;\n-    private int nextRequiredSequence;\n-    private int nextRequiredSubsequence;\n+    /**\n+     * Whether the high watermark has been exceeded.\n+     */\n+    volatile private boolean highWatermarkExceeded = false;\n \n-    private final Queue<OrderedDownstreamChannelEvent> holdingQueue;\n+    /**\n+     * The number of requests in flight\n+     */\n+    private final AtomicInteger inFlight = new AtomicInteger();\n \n+    /**\n+     * A write message with a promise of when it's written.\n+     */\n+    private static class WriteMessage {\n+        /**\n+         * The written message.\n+         */\n+        final SequencedOutboundMessage message;\n+\n+        /**\n+         * The future that is redeemed once the message is written.\n+         */\n+        final ChannelPromise promise;\n+\n+        public WriteMessage(SequencedOutboundMessage message, ChannelPromise promise) {\n+            this.message = message;\n+            this.promise = promise;\n+        }\n+    }\n+\n+    /**\n+     * The buffered events, by sequence\n+     */\n+    private final IntObjectMap<List<WriteMessage>> bufferedEvents = new IntObjectHashMap<>();\n+\n+    private final int inFlightRequestsLowWatermark;\n+    private final int inFlightRequestsHighWatermark;\n+\n+    /**\n+     * Create the pipelining handler with low and high watermarks of 2 and 4.\n+     */\n     public HttpPipeliningHandler() {\n-        this(MAX_EVENTS_HELD);\n+        this(2, 4);\n     }\n \n     /**\n-     * @param maxEventsHeld the maximum number of channel events that will be retained prior to aborting the channel\n-     *                      connection. This is required as events cannot queue up indefintely; we would run out of\n-     *                      memory if this was the case.\n+     * Create the pipelining handler.\n+     *\n+     * @param inFlightRequestsLowWatermark The low watermark for in flight requests, where, if the high watermark has\n+     *                                     been exceeded, the handler will start reading again. Must be at least 0.\n+     * @param inFlightRequestsHighWatermark The high watermark, once in flight requests has exceeded this, the handler\n+     *                                      will stop reading, pushing back on the client.  Must be greater than the\n+     *                                      low watermark.\n      */\n-    public HttpPipeliningHandler(final int maxEventsHeld) {\n-        this.maxEventsHeld = maxEventsHeld;\n-\n-        holdingQueue = new PriorityQueue<OrderedDownstreamChannelEvent>(INITIAL_EVENTS_HELD, new Comparator<OrderedDownstreamChannelEvent>() {\n-            @Override\n-            public int compare(OrderedDownstreamChannelEvent o1, OrderedDownstreamChannelEvent o2) {\n-                final int delta = o1.getOrderedUpstreamMessageEvent().getSequence() - o2.getOrderedUpstreamMessageEvent().getSequence();\n-                if (delta == 0) {\n-                    return o1.getSubsequence() - o2.getSubsequence();\n-                } else {\n-                    return delta;\n-                }\n-            }\n-        });\n+    public HttpPipeliningHandler(int inFlightRequestsLowWatermark, int inFlightRequestsHighWatermark) {\n+        if (inFlightRequestsLowWatermark < 0) {\n+            throw new IllegalArgumentException(\"inFlightRequestsLowWatermark must be an least 0, was \" + inFlightRequestsLowWatermark);\n+        }\n+        if (inFlightRequestsHighWatermark <= inFlightRequestsLowWatermark) {\n+            throw new IllegalArgumentException(\"inFlightRequestsHighWatermark must be greater than inFlightRequestsLowWatermark, but was \" + inFlightRequestsHighWatermark);\n+        }\n+        this.inFlightRequestsLowWatermark = inFlightRequestsLowWatermark;\n+        this.inFlightRequestsHighWatermark = inFlightRequestsHighWatermark;\n     }\n \n-    public int getMaxEventsHeld() {\n-        return maxEventsHeld;\n+    @Override\n+    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {\n+        // Only forward read complete if we haven't exceeded the high watermark\n+        if (!highWatermarkExceeded) {\n+            ctx.fireChannelReadComplete();\n+        }\n     }\n \n     @Override\n-    public void messageReceived(final ChannelHandlerContext ctx, final MessageEvent e) {\n-        final Object msg = e.getMessage();\n+    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n         if (msg instanceof HttpRequest) {\n-            ctx.sendUpstream(new OrderedUpstreamMessageEvent(sequence++, e.getChannel(), msg, e.getRemoteAddress()));\n+            receiveSequence++;\n+\n+            if (inFlight.incrementAndGet() > inFlightRequestsHighWatermark) {\n+                highWatermarkExceeded = true;\n+            }\n+            HttpRequest request = (HttpRequest) msg;\n+            ctx.fireChannelRead(new SequencedHttpRequest(receiveSequence, request));\n+\n         } else {\n-            ctx.sendUpstream(e);\n+            ctx.fireChannelRead(msg);\n         }\n     }\n \n     @Override\n-    public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent e)\n-            throws Exception {\n-        if (e instanceof OrderedDownstreamChannelEvent) {\n-\n-            boolean channelShouldClose = false;\n-\n-            synchronized (holdingQueue) {\n-                if (holdingQueue.size() < maxEventsHeld) {\n-\n-                    final OrderedDownstreamChannelEvent currentEvent = (OrderedDownstreamChannelEvent) e;\n-                    holdingQueue.add(currentEvent);\n-\n-                    while (!holdingQueue.isEmpty()) {\n-                        final OrderedDownstreamChannelEvent nextEvent = holdingQueue.peek();\n-                        if (nextEvent.getOrderedUpstreamMessageEvent().getSequence() != nextRequiredSequence |\n-                                nextEvent.getSubsequence() != nextRequiredSubsequence) {\n-                            break;\n-                        }\n-                        holdingQueue.remove();\n-                        ctx.sendDownstream(nextEvent.getChannelEvent());\n-                        if (nextEvent.isLast()) {\n-                            ++nextRequiredSequence;\n-                            nextRequiredSubsequence = 0;\n-                        } else {\n-                            ++nextRequiredSubsequence;\n-                        }\n-                    }\n+    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {\n+        if (msg instanceof SequencedOutboundMessage) {\n+            SequencedOutboundMessage sequenced = (SequencedOutboundMessage) msg;\n+\n+            synchronizedWrite(ctx, sequenced, promise);\n+        } else {\n+            ctx.write(msg, promise);\n+        }\n+    }\n+\n+    private void progressToNextSendingSequence(ChannelHandlerContext ctx) {\n+        currentlySendingSequence++;\n+\n+        int inFlight = this.inFlight.decrementAndGet();\n+        // If we're now at the low water mark, issue a read for the next request\n+        if (highWatermarkExceeded && inFlight == inFlightRequestsLowWatermark) {\n+            ctx.read();\n+            highWatermarkExceeded = false;\n+        }\n+    }\n+\n+    /**\n+     * Write the next sequences, if buffered.\n+     */\n+    private void flushNextSequences(ChannelHandlerContext ctx) {\n+        synchronized (bufferedEvents) {","path":"src/main/java/com/typesafe/netty/http/pipelining/HttpPipeliningHandler.java","position":235,"original_position":235,"commit_id":"2c25c0be43ec6b0b6d6f2b2a109ef60826ed950c","original_commit_id":"2c25c0be43ec6b0b6d6f2b2a109ef60826ed950c","user":{"login":"hepin1989","id":501740,"avatar_url":"https://avatars.githubusercontent.com/u/501740?v=3","gravatar_id":"","url":"https://api.github.com/users/hepin1989","html_url":"https://github.com/hepin1989","followers_url":"https://api.github.com/users/hepin1989/followers","following_url":"https://api.github.com/users/hepin1989/following{/other_user}","gists_url":"https://api.github.com/users/hepin1989/gists{/gist_id}","starred_url":"https://api.github.com/users/hepin1989/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hepin1989/subscriptions","organizations_url":"https://api.github.com/users/hepin1989/orgs","repos_url":"https://api.github.com/users/hepin1989/repos","events_url":"https://api.github.com/users/hepin1989/events{/privacy}","received_events_url":"https://api.github.com/users/hepin1989/received_events","type":"User","site_admin":false},"body":"does the ```HttpPipeliningHandler``` shareable?If it is it should marked as ```@Shareable```,otherwise ,I don't think we need a synchronized here.","created_at":"2015-06-26T05:28:52Z","updated_at":"2015-06-26T05:28:52Z","html_url":"https://github.com/typesafehub/netty-http-pipelining/pull/14#discussion_r33329543","pull_request_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/pulls/14","_links":{"self":{"href":"https://api.github.com/repos/typesafehub/netty-http-pipelining/pulls/comments/33329543"},"html":{"href":"https://github.com/typesafehub/netty-http-pipelining/pull/14#discussion_r33329543"},"pull_request":{"href":"https://api.github.com/repos/typesafehub/netty-http-pipelining/pulls/14"}}},"pull_request":{"url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/pulls/14","id":38616055,"html_url":"https://github.com/typesafehub/netty-http-pipelining/pull/14","diff_url":"https://github.com/typesafehub/netty-http-pipelining/pull/14.diff","patch_url":"https://github.com/typesafehub/netty-http-pipelining/pull/14.patch","issue_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/issues/14","number":14,"state":"open","locked":false,"title":"Upgraded to Netty 4","user":{"login":"jroper","id":105833,"avatar_url":"https://avatars.githubusercontent.com/u/105833?v=3","gravatar_id":"","url":"https://api.github.com/users/jroper","html_url":"https://github.com/jroper","followers_url":"https://api.github.com/users/jroper/followers","following_url":"https://api.github.com/users/jroper/following{/other_user}","gists_url":"https://api.github.com/users/jroper/gists{/gist_id}","starred_url":"https://api.github.com/users/jroper/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jroper/subscriptions","organizations_url":"https://api.github.com/users/jroper/orgs","repos_url":"https://api.github.com/users/jroper/repos","events_url":"https://api.github.com/users/jroper/events{/privacy}","received_events_url":"https://api.github.com/users/jroper/received_events","type":"User","site_admin":false},"body":"In addition, made the following changes to the way the handler works:\n\n* Messages are buffered in a hash map by sequence ID if it's not the right time for the sequence.\n* Removed subsequence ordering - writes for a given request are required to be sequential without the pipelining handler, so with it we can assume the same.\n* Replaced buffer limit with back pressure - if the number of in flight requests exceeds a high water mark, the handler now exerts back pressure on the client by blocking read completed events, and unblocks them, and requests a new read, when a low watermark is reached.","created_at":"2015-06-26T04:15:06Z","updated_at":"2015-06-26T05:28:52Z","closed_at":null,"merged_at":null,"merge_commit_sha":"ee45da46dfce55d23b097cdcfabe37f2ade7710d","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/pulls/14/commits","review_comments_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/pulls/14/comments","review_comment_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/pulls/comments{/number}","comments_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/issues/14/comments","statuses_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/statuses/2c25c0be43ec6b0b6d6f2b2a109ef60826ed950c","head":{"label":"jroper:netty-4","ref":"netty-4","sha":"2c25c0be43ec6b0b6d6f2b2a109ef60826ed950c","user":{"login":"jroper","id":105833,"avatar_url":"https://avatars.githubusercontent.com/u/105833?v=3","gravatar_id":"","url":"https://api.github.com/users/jroper","html_url":"https://github.com/jroper","followers_url":"https://api.github.com/users/jroper/followers","following_url":"https://api.github.com/users/jroper/following{/other_user}","gists_url":"https://api.github.com/users/jroper/gists{/gist_id}","starred_url":"https://api.github.com/users/jroper/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jroper/subscriptions","organizations_url":"https://api.github.com/users/jroper/orgs","repos_url":"https://api.github.com/users/jroper/repos","events_url":"https://api.github.com/users/jroper/events{/privacy}","received_events_url":"https://api.github.com/users/jroper/received_events","type":"User","site_admin":false},"repo":{"id":33710809,"name":"netty-http-pipelining","full_name":"jroper/netty-http-pipelining","owner":{"login":"jroper","id":105833,"avatar_url":"https://avatars.githubusercontent.com/u/105833?v=3","gravatar_id":"","url":"https://api.github.com/users/jroper","html_url":"https://github.com/jroper","followers_url":"https://api.github.com/users/jroper/followers","following_url":"https://api.github.com/users/jroper/following{/other_user}","gists_url":"https://api.github.com/users/jroper/gists{/gist_id}","starred_url":"https://api.github.com/users/jroper/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jroper/subscriptions","organizations_url":"https://api.github.com/users/jroper/orgs","repos_url":"https://api.github.com/users/jroper/repos","events_url":"https://api.github.com/users/jroper/events{/privacy}","received_events_url":"https://api.github.com/users/jroper/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/jroper/netty-http-pipelining","description":"Adds http pipelining capability to Netty","fork":true,"url":"https://api.github.com/repos/jroper/netty-http-pipelining","forks_url":"https://api.github.com/repos/jroper/netty-http-pipelining/forks","keys_url":"https://api.github.com/repos/jroper/netty-http-pipelining/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jroper/netty-http-pipelining/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jroper/netty-http-pipelining/teams","hooks_url":"https://api.github.com/repos/jroper/netty-http-pipelining/hooks","issue_events_url":"https://api.github.com/repos/jroper/netty-http-pipelining/issues/events{/number}","events_url":"https://api.github.com/repos/jroper/netty-http-pipelining/events","assignees_url":"https://api.github.com/repos/jroper/netty-http-pipelining/assignees{/user}","branches_url":"https://api.github.com/repos/jroper/netty-http-pipelining/branches{/branch}","tags_url":"https://api.github.com/repos/jroper/netty-http-pipelining/tags","blobs_url":"https://api.github.com/repos/jroper/netty-http-pipelining/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jroper/netty-http-pipelining/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jroper/netty-http-pipelining/git/refs{/sha}","trees_url":"https://api.github.com/repos/jroper/netty-http-pipelining/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jroper/netty-http-pipelining/statuses/{sha}","languages_url":"https://api.github.com/repos/jroper/netty-http-pipelining/languages","stargazers_url":"https://api.github.com/repos/jroper/netty-http-pipelining/stargazers","contributors_url":"https://api.github.com/repos/jroper/netty-http-pipelining/contributors","subscribers_url":"https://api.github.com/repos/jroper/netty-http-pipelining/subscribers","subscription_url":"https://api.github.com/repos/jroper/netty-http-pipelining/subscription","commits_url":"https://api.github.com/repos/jroper/netty-http-pipelining/commits{/sha}","git_commits_url":"https://api.github.com/repos/jroper/netty-http-pipelining/git/commits{/sha}","comments_url":"https://api.github.com/repos/jroper/netty-http-pipelining/comments{/number}","issue_comment_url":"https://api.github.com/repos/jroper/netty-http-pipelining/issues/comments{/number}","contents_url":"https://api.github.com/repos/jroper/netty-http-pipelining/contents/{+path}","compare_url":"https://api.github.com/repos/jroper/netty-http-pipelining/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jroper/netty-http-pipelining/merges","archive_url":"https://api.github.com/repos/jroper/netty-http-pipelining/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jroper/netty-http-pipelining/downloads","issues_url":"https://api.github.com/repos/jroper/netty-http-pipelining/issues{/number}","pulls_url":"https://api.github.com/repos/jroper/netty-http-pipelining/pulls{/number}","milestones_url":"https://api.github.com/repos/jroper/netty-http-pipelining/milestones{/number}","notifications_url":"https://api.github.com/repos/jroper/netty-http-pipelining/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jroper/netty-http-pipelining/labels{/name}","releases_url":"https://api.github.com/repos/jroper/netty-http-pipelining/releases{/id}","created_at":"2015-04-10T05:46:11Z","updated_at":"2015-04-10T05:46:11Z","pushed_at":"2015-06-26T04:42:36Z","git_url":"git://github.com/jroper/netty-http-pipelining.git","ssh_url":"git@github.com:jroper/netty-http-pipelining.git","clone_url":"https://github.com/jroper/netty-http-pipelining.git","svn_url":"https://github.com/jroper/netty-http-pipelining","homepage":null,"size":143,"stargazers_count":0,"watchers_count":0,"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":0,"default_branch":"master"}},"base":{"label":"typesafehub:master","ref":"master","sha":"e8c69bd76f371c2b67cbabee07da201ca4c979ea","user":{"login":"typesafehub","id":784923,"avatar_url":"https://avatars.githubusercontent.com/u/784923?v=3","gravatar_id":"","url":"https://api.github.com/users/typesafehub","html_url":"https://github.com/typesafehub","followers_url":"https://api.github.com/users/typesafehub/followers","following_url":"https://api.github.com/users/typesafehub/following{/other_user}","gists_url":"https://api.github.com/users/typesafehub/gists{/gist_id}","starred_url":"https://api.github.com/users/typesafehub/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/typesafehub/subscriptions","organizations_url":"https://api.github.com/users/typesafehub/orgs","repos_url":"https://api.github.com/users/typesafehub/repos","events_url":"https://api.github.com/users/typesafehub/events{/privacy}","received_events_url":"https://api.github.com/users/typesafehub/received_events","type":"Organization","site_admin":false},"repo":{"id":9187774,"name":"netty-http-pipelining","full_name":"typesafehub/netty-http-pipelining","owner":{"login":"typesafehub","id":784923,"avatar_url":"https://avatars.githubusercontent.com/u/784923?v=3","gravatar_id":"","url":"https://api.github.com/users/typesafehub","html_url":"https://github.com/typesafehub","followers_url":"https://api.github.com/users/typesafehub/followers","following_url":"https://api.github.com/users/typesafehub/following{/other_user}","gists_url":"https://api.github.com/users/typesafehub/gists{/gist_id}","starred_url":"https://api.github.com/users/typesafehub/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/typesafehub/subscriptions","organizations_url":"https://api.github.com/users/typesafehub/orgs","repos_url":"https://api.github.com/users/typesafehub/repos","events_url":"https://api.github.com/users/typesafehub/events{/privacy}","received_events_url":"https://api.github.com/users/typesafehub/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/typesafehub/netty-http-pipelining","description":"Adds http pipelining capability to Netty","fork":false,"url":"https://api.github.com/repos/typesafehub/netty-http-pipelining","forks_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/forks","keys_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/keys{/key_id}","collaborators_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/teams","hooks_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/hooks","issue_events_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/issues/events{/number}","events_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/events","assignees_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/assignees{/user}","branches_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/branches{/branch}","tags_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/tags","blobs_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/git/refs{/sha}","trees_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/git/trees{/sha}","statuses_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/statuses/{sha}","languages_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/languages","stargazers_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/stargazers","contributors_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/contributors","subscribers_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/subscribers","subscription_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/subscription","commits_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/commits{/sha}","git_commits_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/git/commits{/sha}","comments_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/comments{/number}","issue_comment_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/issues/comments{/number}","contents_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/contents/{+path}","compare_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/compare/{base}...{head}","merges_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/merges","archive_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/downloads","issues_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/issues{/number}","pulls_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/pulls{/number}","milestones_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/milestones{/number}","notifications_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/labels{/name}","releases_url":"https://api.github.com/repos/typesafehub/netty-http-pipelining/releases{/id}","created_at":"2013-04-03T05:56:04Z","updated_at":"2015-06-23T06:43:55Z","pushed_at":"2015-06-26T04:42:36Z","git_url":"git://github.com/typesafehub/netty-http-pipelining.git","ssh_url":"git@github.com:typesafehub/netty-http-pipelining.git","clone_url":"https://github.com/typesafehub/netty-http-pipelining.git","svn_url":"https://github.com/typesafehub/netty-http-pipelining","homepage":null,"size":356,"stargazers_count":14,"watchers_count":14,"language":"Java","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":7,"mirror_url":null,"open_issues_count":2,"forks":7,"open_issues":2,"watchers":14,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/typesafehub/netty-http-pipelining/pulls/14"},"html":{"href":"https://github.com/typesafehub/netty-http-pipelining/pull/14"},"issue":{"href":"https://api.github.com/repos/typesafehub/netty-http-pipelining/issues/14"},"comments":{"href":"https://api.github.com/repos/typesafehub/netty-http-pipelining/issues/14/comments"},"review_comments":{"href":"https://api.github.com/repos/typesafehub/netty-http-pipelining/pulls/14/comments"},"review_comment":{"href":"https://api.github.com/repos/typesafehub/netty-http-pipelining/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/typesafehub/netty-http-pipelining/pulls/14/commits"},"statuses":{"href":"https://api.github.com/repos/typesafehub/netty-http-pipelining/statuses/2c25c0be43ec6b0b6d6f2b2a109ef60826ed950c"}}}}