├── .gitattributes ├── .gitignore ├── README.md ├── pom.xml └── src └── com └── zhang ├── client ├── NettyHttpClient.java ├── NettyHttpRequest.java ├── NettyHttpResponse.java ├── NettyHttpResponseFuture.java └── test │ └── NettyClientTest.java ├── handler ├── AdditionalChannelInitializer.java └── NettyChannelPoolHandler.java ├── pool └── NettyChannelPool.java └── util ├── NettyHttpRequestUtil.java ├── NettyHttpResponseBuilder.java └── NettyHttpResponseFutureUtil.java /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # light-netty-client 2 | Light-netty-client is a light framework for rapid development of asynchronous clients based on netty 4.x. 3 | 4 | The sample code for sending get request: 5 | 6 | String getUrl = "http://www.baidu.com:80"; 7 | NettyHttpRequest request = new NettyHttpRequest(); 8 | request.header(HttpHeaders.Names.CONTENT_TYPE, "text/json; charset=GBK").uri(getUrl); 9 | 10 | NettyHttpClient client = new NettyHttpClient.ConfigBuilder() 11 | .maxIdleTimeInMilliSecondes(200 * 1000) 12 | .connectTimeOutInMilliSecondes(30 * 1000).build(); 13 | 14 | NettyHttpResponseFuture responseFuture = client.doGet(request); 15 | NettyHttpResponse result = (NettyHttpResponse) responseFuture.get(); 16 | client.close(); 17 | The sample code for sending post request: 18 | 19 | String postUrl = "http://www.xxx.com:8080/testPost"; 20 | String postContent = "";//json format 21 | NettyHttpRequest request = new NettyHttpRequest(); 22 | request.header(HttpHeaders.Names.CONTENT_TYPE, "text/json; charset=GBK").uri(postUrl).content( 23 | postContent, null); 24 | 25 | NettyHttpClient client = new NettyHttpClient.ConfigBuilder() 26 | .maxIdleTimeInMilliSecondes(200 * 1000) 27 | .connectTimeOutInMilliSecondes(30 * 1000) 28 | .build(); 29 | 30 | NettyHttpResponseFuture responseFuture = client.doPost(request); 31 | NettyHttpResponse result = (NettyHttpResponse) responseFuture.get(); 32 | client.close(); 33 | You can config the maximum number of channels to allow in the channel pool like this: 34 | 35 | Map maxPerRoute = new HashMap(); 36 | maxPerRoute.put("www.baidu.com:80", 100); 37 | 38 | NettyHttpClient client = new NettyHttpClient.ConfigBuilder() 39 | .maxIdleTimeInMilliSecondes(200 * 1000) 40 | .connectTimeOutInMilliSecondes(30 * 1000) 41 | .maxPerRoute(maxPerRoute) 42 | .build(); 43 | If you not config this, the default value is 200. 44 | 45 | 46 | For more information,please refer to the following blog 47 | http://xw-z1985.iteye.com/blog/2180873 48 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | com.zhang 6 | light-netty-client 7 | A light client based on netty 8 | jar 9 | 1.0.0 10 | 11 | 12 | UTF-8 13 | 1.6 14 | 15 | 16 | 17 | 18 | jason 19 | jason 20 | xw_znwpu@163.com 21 | 22 | 23 | 24 | 25 | 26 | 27 | org.apache.maven.plugins 28 | maven-compiler-plugin 29 | 2.5.1 30 | 31 | UTF-8 32 | ${jdk.version} 33 | ${jdk.version} 34 | 35 | 36 | 37 | 38 | org.apache.maven.plugins 39 | maven-source-plugin 40 | 2.1.1 41 | 42 | 43 | attach-sources 44 | 45 | jar-no-fork 46 | 47 | 48 | 49 | 50 | true 51 | 52 | 53 | 54 | 55 | maven-surefire-plugin 56 | 2.12 57 | 58 | 59 | **/*Tests.java 60 | 61 | -Xmx256m 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | io.netty 71 | netty-all 72 | 4.0.25.Final 73 | compile 74 | 75 | 76 | 77 | 78 | junit 79 | junit 80 | 4.9 81 | test 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/com/zhang/client/NettyHttpClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 The LightNettyClient Project 3 | * 4 | * The Light netty client Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package com.zhang.client; 17 | 18 | import io.netty.channel.ChannelOption; 19 | import io.netty.channel.EventLoopGroup; 20 | import io.netty.handler.codec.http.HttpMethod; 21 | import io.netty.handler.codec.http.HttpRequest; 22 | 23 | import java.net.InetSocketAddress; 24 | import java.util.HashMap; 25 | import java.util.Map; 26 | 27 | import com.zhang.handler.AdditionalChannelInitializer; 28 | import com.zhang.pool.NettyChannelPool; 29 | import com.zhang.util.NettyHttpRequestUtil; 30 | 31 | /** 32 | * @author xianwu.zhang 33 | */ 34 | public class NettyHttpClient { 35 | 36 | private NettyChannelPool channelPool; 37 | 38 | private ConfigBuilder configBuilder; 39 | 40 | private NettyHttpClient(ConfigBuilder configBuilder) { 41 | this.configBuilder = configBuilder; 42 | this.channelPool = new NettyChannelPool(configBuilder.getMaxPerRoute(), configBuilder 43 | .getConnectTimeOutInMilliSecondes(), configBuilder.getMaxIdleTimeInMilliSecondes(), 44 | configBuilder.getForbidForceConnect(), configBuilder.getAdditionalChannelInitializer(), 45 | configBuilder.getOptions(), configBuilder.getGroup()); 46 | } 47 | 48 | public NettyHttpResponseFuture doPost(NettyHttpRequest request) throws Exception { 49 | 50 | HttpRequest httpRequest = NettyHttpRequestUtil.create(request, HttpMethod.POST); 51 | InetSocketAddress route = new InetSocketAddress(request.getUri().getHost(), request 52 | .getUri().getPort()); 53 | 54 | return channelPool.sendRequest(route, httpRequest); 55 | } 56 | 57 | public NettyHttpResponseFuture doGet(NettyHttpRequest request) throws Exception { 58 | HttpRequest httpRequest = NettyHttpRequestUtil.create(request, HttpMethod.GET); 59 | InetSocketAddress route = new InetSocketAddress(request.getUri().getHost(), request 60 | .getUri().getPort()); 61 | return channelPool.sendRequest(route, httpRequest); 62 | } 63 | 64 | public void close() throws InterruptedException { 65 | channelPool.close(); 66 | } 67 | 68 | public ConfigBuilder getConfigBuilder() { 69 | return configBuilder; 70 | } 71 | 72 | public void setConfigBuilder(ConfigBuilder configBuilder) { 73 | this.configBuilder = configBuilder; 74 | } 75 | 76 | public static final class ConfigBuilder { 77 | @SuppressWarnings("unchecked") 78 | private Map options = new HashMap(); 79 | 80 | // max idle time for a channel before close 81 | private int maxIdleTimeInMilliSecondes; 82 | 83 | // max time wait for a channel return from pool 84 | private int connectTimeOutInMilliSecondes; 85 | 86 | /** 87 | * value is false indicates that when there is not any channel in pool and no new 88 | * channel allowed to be create based on maxPerRoute, a new channel will be forced 89 | * to create.Otherwise, a TimeoutException will be thrown 90 | * value is false. 91 | */ 92 | private boolean forbidForceConnect = false; 93 | 94 | private AdditionalChannelInitializer additionalChannelInitializer; 95 | 96 | // max number of channels allow to be created per route 97 | private Map maxPerRoute; 98 | 99 | private EventLoopGroup customGroup; 100 | 101 | public ConfigBuilder() { 102 | } 103 | 104 | public NettyHttpClient build() { 105 | return new NettyHttpClient(this); 106 | } 107 | 108 | public ConfigBuilder maxPerRoute(Map maxPerRoute) { 109 | this.maxPerRoute = maxPerRoute; 110 | return this; 111 | } 112 | 113 | public ConfigBuilder connectTimeOutInMilliSecondes(int connectTimeOutInMilliSecondes) { 114 | this.connectTimeOutInMilliSecondes = connectTimeOutInMilliSecondes; 115 | return this; 116 | } 117 | 118 | @SuppressWarnings("unchecked") 119 | public ConfigBuilder option(ChannelOption key, Object value) { 120 | options.put(key, value); 121 | return this; 122 | } 123 | 124 | public ConfigBuilder maxIdleTimeInMilliSecondes(int maxIdleTimeInMilliSecondes) { 125 | this.maxIdleTimeInMilliSecondes = maxIdleTimeInMilliSecondes; 126 | return this; 127 | } 128 | 129 | public ConfigBuilder additionalChannelInitializer( 130 | AdditionalChannelInitializer additionalChannelInitializer) { 131 | this.additionalChannelInitializer = additionalChannelInitializer; 132 | return this; 133 | } 134 | 135 | public ConfigBuilder customGroup(EventLoopGroup customGroup) { 136 | this.customGroup = customGroup; 137 | return this; 138 | } 139 | 140 | public ConfigBuilder forbidForceConnect(boolean forbidForceConnect) { 141 | this.forbidForceConnect = forbidForceConnect; 142 | return this; 143 | } 144 | 145 | @SuppressWarnings("unchecked") 146 | public Map getOptions() { 147 | return options; 148 | } 149 | 150 | public int getMaxIdleTimeInMilliSecondes() { 151 | return maxIdleTimeInMilliSecondes; 152 | } 153 | 154 | public AdditionalChannelInitializer getAdditionalChannelInitializer() { 155 | return additionalChannelInitializer; 156 | } 157 | 158 | public Map getMaxPerRoute() { 159 | return maxPerRoute; 160 | } 161 | 162 | public int getConnectTimeOutInMilliSecondes() { 163 | return connectTimeOutInMilliSecondes; 164 | } 165 | 166 | public EventLoopGroup getGroup() { 167 | return this.customGroup; 168 | } 169 | 170 | public boolean getForbidForceConnect() { 171 | return this.forbidForceConnect; 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/com/zhang/client/NettyHttpRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 The LightNettyClient Project 3 | * 4 | * The Light netty client Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package com.zhang.client; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.buffer.Unpooled; 20 | 21 | import java.net.URI; 22 | import java.nio.charset.Charset; 23 | import java.util.HashMap; 24 | import java.util.Map; 25 | 26 | /** 27 | * @author xianwu.zhang 28 | */ 29 | public class NettyHttpRequest { 30 | 31 | private URI uri; 32 | 33 | private Map headers; 34 | 35 | private ByteBuf content; 36 | 37 | private static final Charset DEFAUT_CHARSET = Charset.forName("GBK"); 38 | 39 | public NettyHttpRequest uri(String uri) { 40 | this.uri = URI.create(uri); 41 | return this; 42 | } 43 | 44 | public NettyHttpRequest uri(URI uri) { 45 | if (null == uri) { 46 | throw new NullPointerException("uri"); 47 | } 48 | this.uri = uri; 49 | return this; 50 | } 51 | 52 | public NettyHttpRequest header(String key, Object value) { 53 | if (null == this.headers) { 54 | this.headers = new HashMap(); 55 | } 56 | headers.put(key, value); 57 | return this; 58 | } 59 | 60 | public NettyHttpRequest headers(Map headers) { 61 | if (null == headers) { 62 | throw new NullPointerException("headers"); 63 | } 64 | 65 | if (null == this.headers) { 66 | this.headers = new HashMap(); 67 | } 68 | 69 | this.headers.putAll(headers); 70 | return this; 71 | } 72 | 73 | public NettyHttpRequest content(ByteBuf content) { 74 | if (null == content) { 75 | throw new NullPointerException("content"); 76 | } 77 | 78 | this.content = content; 79 | return this; 80 | } 81 | 82 | public NettyHttpRequest content(byte[] content) { 83 | if (null == content) { 84 | throw new NullPointerException("content"); 85 | } 86 | this.content = Unpooled.copiedBuffer(content); 87 | return this; 88 | } 89 | 90 | public NettyHttpRequest content(String content, Charset charset) { 91 | if (null == content) { 92 | throw new NullPointerException("content"); 93 | } 94 | charset = null == charset ? DEFAUT_CHARSET : charset; 95 | this.content = Unpooled.copiedBuffer(content, charset); 96 | return this; 97 | } 98 | 99 | public URI getUri() { 100 | return uri; 101 | } 102 | 103 | public Map getHeaders() { 104 | return headers; 105 | } 106 | 107 | public ByteBuf getContent() { 108 | return content; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/com/zhang/client/NettyHttpResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 The LightNettyClient Project 3 | * 4 | * The Light netty client Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package com.zhang.client; 17 | 18 | import java.nio.charset.Charset; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | import io.netty.buffer.ByteBuf; 23 | import io.netty.handler.codec.http.HttpHeaders; 24 | import io.netty.handler.codec.http.HttpResponseStatus; 25 | import io.netty.handler.codec.http.HttpVersion; 26 | 27 | /** 28 | * @author xianwu.zhang 29 | */ 30 | public class NettyHttpResponse { 31 | private volatile boolean success = false; 32 | private volatile HttpResponseStatus status; 33 | private volatile HttpVersion version; 34 | private volatile HttpHeaders headers; 35 | private volatile List contents; 36 | private volatile Throwable cause; 37 | 38 | public NettyHttpResponse() { 39 | super(); 40 | } 41 | 42 | public String getResponseBody() { 43 | return getResponseBody(Charset.forName("GBK")); 44 | } 45 | 46 | public String getResponseBody(Charset charset) { 47 | if (null == contents || 0 == contents.size()) { 48 | return null; 49 | } 50 | StringBuilder responseBody = new StringBuilder(); 51 | for (ByteBuf content : contents) { 52 | responseBody.append(content.toString(charset)); 53 | } 54 | 55 | return responseBody.toString(); 56 | } 57 | 58 | public void addContent(ByteBuf byteBuf) { 59 | if (null == contents) { 60 | contents = new ArrayList(); 61 | } 62 | contents.add(byteBuf); 63 | } 64 | 65 | /** 66 | * @return the version 67 | */ 68 | public HttpVersion getVersion() { 69 | return version; 70 | } 71 | 72 | /** 73 | * @param version 74 | * the version to set 75 | */ 76 | public void setVersion(HttpVersion version) { 77 | this.version = version; 78 | } 79 | 80 | /** 81 | * @return the contents 82 | */ 83 | public List getContents() { 84 | return contents; 85 | } 86 | 87 | /** 88 | * @param contents the contents to set 89 | */ 90 | public void setContents(List contents) { 91 | this.contents = contents; 92 | } 93 | 94 | /** 95 | * @return the headers 96 | */ 97 | public HttpHeaders getHeaders() { 98 | return headers; 99 | } 100 | 101 | /** 102 | * @param headers the headers to set 103 | */ 104 | public void setHeaders(HttpHeaders headers) { 105 | this.headers = headers; 106 | } 107 | 108 | /** 109 | * @return the status 110 | */ 111 | public HttpResponseStatus getStatus() { 112 | return status; 113 | } 114 | 115 | /** 116 | * @param status the status to set 117 | */ 118 | public void setStatus(HttpResponseStatus status) { 119 | this.status = status; 120 | } 121 | 122 | /** 123 | * Getter method for property success. 124 | * 125 | * @return property value of success 126 | */ 127 | public boolean isSuccess() { 128 | return success; 129 | } 130 | 131 | /** 132 | * Setter method for property success. 133 | * 134 | * @param success value to be assigned to property success 135 | */ 136 | public void setSuccess(boolean success) { 137 | this.success = success; 138 | } 139 | 140 | /** 141 | * Getter method for property cause. 142 | * 143 | * @return property value of cause 144 | */ 145 | public Throwable getCause() { 146 | return cause; 147 | } 148 | 149 | /** 150 | * Setter method for property cause. 151 | * 152 | * @param cause value to be assigned to property cause 153 | */ 154 | public void setCause(Throwable cause) { 155 | this.cause = cause; 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /src/com/zhang/client/NettyHttpResponseFuture.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 The LightNettyClient Project 3 | * 4 | * The Light netty client Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package com.zhang.client; 17 | 18 | import io.netty.channel.Channel; 19 | 20 | import java.util.concurrent.CountDownLatch; 21 | import java.util.concurrent.ExecutionException; 22 | import java.util.concurrent.TimeUnit; 23 | import java.util.concurrent.TimeoutException; 24 | import java.util.concurrent.atomic.AtomicBoolean; 25 | 26 | import com.zhang.util.NettyHttpResponseBuilder; 27 | 28 | /** 29 | * @author xianwu.zhang 30 | */ 31 | public class NettyHttpResponseFuture { 32 | private final CountDownLatch latch = new CountDownLatch(1); 33 | 34 | private volatile boolean isDone = false; 35 | 36 | private volatile boolean isCancel = false; 37 | 38 | private final AtomicBoolean isProcessed = new AtomicBoolean(false); 39 | 40 | private volatile NettyHttpResponseBuilder responseBuilder; 41 | 42 | private volatile Channel channel; 43 | 44 | public boolean cancel(Throwable cause) { 45 | if (isProcessed.getAndSet(true)) { 46 | return false; 47 | } 48 | 49 | responseBuilder = new NettyHttpResponseBuilder(); 50 | responseBuilder.setSuccess(false); 51 | responseBuilder.setCause(cause); 52 | isCancel = true; 53 | latch.countDown(); 54 | return true; 55 | } 56 | 57 | public NettyHttpResponse get() throws InterruptedException, ExecutionException { 58 | latch.await(); 59 | return responseBuilder.build(); 60 | } 61 | 62 | public NettyHttpResponse get(long timeout, TimeUnit unit) throws TimeoutException, 63 | InterruptedException { 64 | if (!latch.await(timeout, unit)) { 65 | throw new TimeoutException(); 66 | } 67 | return responseBuilder.build(); 68 | } 69 | 70 | public boolean done() { 71 | if (isProcessed.getAndSet(true)) { 72 | return false; 73 | } 74 | isDone = true; 75 | latch.countDown(); 76 | return true; 77 | } 78 | 79 | public boolean isCancelled() { 80 | return isCancel; 81 | } 82 | 83 | public boolean isDone() { 84 | return isDone; 85 | } 86 | 87 | /** 88 | * Getter method for property channel. 89 | * 90 | * @return property value of channel 91 | */ 92 | public Channel getChannel() { 93 | return channel; 94 | } 95 | 96 | /** 97 | * Setter method for property channel. 98 | * 99 | * @param channel value to be assigned to property channel 100 | */ 101 | public void setChannel(Channel channel) { 102 | this.channel = channel; 103 | } 104 | 105 | /** 106 | * Getter method for property responseBuilder. 107 | * 108 | * @return property value of responseBuilder 109 | */ 110 | public NettyHttpResponseBuilder getResponseBuilder() { 111 | return responseBuilder; 112 | } 113 | 114 | /** 115 | * Setter method for property responseBuilder. 116 | * 117 | * @param responseBuilder value to be assigned to property responseBuilder 118 | */ 119 | public void setResponseBuilder(NettyHttpResponseBuilder responseBuilder) { 120 | this.responseBuilder = responseBuilder; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/com/zhang/client/test/NettyClientTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 The LightNettyClient Project 3 | * 4 | * The Light netty client Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package com.zhang.client.test; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.handler.codec.http.HttpHeaders; 20 | import io.netty.util.CharsetUtil; 21 | 22 | import java.util.HashMap; 23 | import java.util.Map; 24 | 25 | import org.junit.Test; 26 | 27 | import com.zhang.client.NettyHttpClient; 28 | import com.zhang.client.NettyHttpRequest; 29 | import com.zhang.client.NettyHttpResponse; 30 | import com.zhang.client.NettyHttpResponseFuture; 31 | 32 | /** 33 | * @author xianwu.zhang 34 | */ 35 | public class NettyClientTest { 36 | 37 | @Test 38 | public void testGet() throws Exception { 39 | final String url = "http://www.baidu.com:80"; 40 | 41 | Map maxPerRoute = new HashMap(); 42 | maxPerRoute.put("www.baidu.com:80", 100); 43 | 44 | final NettyHttpClient client = new NettyHttpClient.ConfigBuilder() 45 | .maxIdleTimeInMilliSecondes(200 * 1000).maxPerRoute(maxPerRoute) 46 | .connectTimeOutInMilliSecondes(30 * 1000).build(); 47 | 48 | final NettyHttpRequest request = new NettyHttpRequest(); 49 | request.header(HttpHeaders.Names.CONTENT_TYPE, "text/json; charset=GBK").uri(url); 50 | 51 | NettyHttpResponseFuture responseFuture = client.doGet(request); 52 | NettyHttpResponse response = (NettyHttpResponse) responseFuture.get(); 53 | client.close(); 54 | 55 | print(response); 56 | } 57 | 58 | //@Test 59 | public void testPost() throws Exception { 60 | final String postUrl = "http://www.xxx.com:8080/testPost"; 61 | final String postContent = "";// json format 62 | 63 | Map maxPerRoute = new HashMap(); 64 | maxPerRoute.put("www.xxx.com:80", 100); 65 | 66 | final NettyHttpClient client = new NettyHttpClient.ConfigBuilder() 67 | .maxIdleTimeInMilliSecondes(200 * 1000).maxPerRoute(maxPerRoute) 68 | .connectTimeOutInMilliSecondes(30 * 1000).build(); 69 | 70 | final NettyHttpRequest request = new NettyHttpRequest(); 71 | request.header(HttpHeaders.Names.CONTENT_TYPE, "text/json; charset=GBK").uri(postUrl) 72 | .content(postContent, null); 73 | 74 | NettyHttpResponseFuture responseFuture = client.doPost(request); 75 | NettyHttpResponse response = (NettyHttpResponse) responseFuture.get(); 76 | client.close(); 77 | print(response); 78 | } 79 | 80 | private void print(NettyHttpResponse response) { 81 | System.out.println("STATUS: " + response.getStatus()); 82 | System.out.println("VERSION: " + response.getVersion()); 83 | System.out.println(); 84 | 85 | if (!response.getHeaders().isEmpty()) { 86 | for (String name : response.getHeaders().names()) { 87 | for (String value : response.getHeaders().getAll(name)) { 88 | System.out.println("HEADER: " + name + " = " + value); 89 | } 90 | } 91 | } 92 | System.out.println("CHUNKED CONTENT :"); 93 | for (ByteBuf buf : response.getContents()) { 94 | System.out.print(buf.toString(CharsetUtil.UTF_8)); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/com/zhang/handler/AdditionalChannelInitializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 The LightNettyClient Project 3 | * 4 | * The Light netty client Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package com.zhang.handler; 17 | 18 | import io.netty.channel.Channel; 19 | 20 | /** 21 | * @author xianwu.zhang 22 | */ 23 | public interface AdditionalChannelInitializer { 24 | 25 | void initChannel(Channel ch) throws Exception; 26 | } 27 | -------------------------------------------------------------------------------- /src/com/zhang/handler/NettyChannelPoolHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 The LightNettyClient Project 3 | * 4 | * The Light netty client Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package com.zhang.handler; 17 | 18 | import io.netty.channel.ChannelHandlerContext; 19 | import io.netty.channel.SimpleChannelInboundHandler; 20 | import io.netty.handler.codec.http.HttpContent; 21 | import io.netty.handler.codec.http.HttpObject; 22 | import io.netty.handler.codec.http.HttpResponse; 23 | import io.netty.handler.codec.http.LastHttpContent; 24 | import io.netty.handler.timeout.IdleStateEvent; 25 | 26 | import java.util.logging.Level; 27 | import java.util.logging.Logger; 28 | 29 | import com.zhang.pool.NettyChannelPool; 30 | import com.zhang.util.NettyHttpResponseFutureUtil; 31 | 32 | /** 33 | * @author xianwu.zhang 34 | */ 35 | public class NettyChannelPoolHandler extends SimpleChannelInboundHandler { 36 | private static final Logger logger = Logger.getLogger(NettyChannelPoolHandler.class.getName()); 37 | 38 | private NettyChannelPool channelPool; 39 | 40 | /** 41 | * @param channelPool 42 | */ 43 | public NettyChannelPoolHandler(NettyChannelPool channelPool) { 44 | super(); 45 | this.channelPool = channelPool; 46 | } 47 | 48 | @Override 49 | protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception { 50 | if (msg instanceof HttpResponse) { 51 | HttpResponse headers = (HttpResponse) msg; 52 | NettyHttpResponseFutureUtil.setPendingResponse(ctx.channel(), headers); 53 | } 54 | if (msg instanceof HttpContent) { 55 | HttpContent httpContent = (HttpContent) msg; 56 | NettyHttpResponseFutureUtil.setPendingContent(ctx.channel(), httpContent); 57 | if (httpContent instanceof LastHttpContent) { 58 | boolean connectionClose = NettyHttpResponseFutureUtil 59 | .headerContainConnectionClose(ctx.channel()); 60 | 61 | NettyHttpResponseFutureUtil.done(ctx.channel()); 62 | //the maxKeepAliveRequests config will cause server close the channel, and return 'Connection: close' in headers 63 | if (!connectionClose) { 64 | channelPool.returnChannel(ctx.channel()); 65 | } 66 | } 67 | } 68 | } 69 | 70 | /** 71 | * @see io.netty.channel.ChannelInboundHandlerAdapter#userEventTriggered(io.netty.channel.ChannelHandlerContext, java.lang.Object) 72 | */ 73 | @Override 74 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { 75 | if (evt instanceof IdleStateEvent) { 76 | logger.log(Level.WARNING, "remove idle channel: " + ctx.channel()); 77 | ctx.channel().close(); 78 | } else { 79 | ctx.fireUserEventTriggered(evt); 80 | } 81 | } 82 | 83 | /** 84 | * @param channelPool 85 | * the channelPool to set 86 | */ 87 | public void setChannelPool(NettyChannelPool channelPool) { 88 | this.channelPool = channelPool; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/com/zhang/pool/NettyChannelPool.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 The LightNettyClient Project 3 | * 4 | * The Light netty client Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package com.zhang.pool; 17 | 18 | import io.netty.bootstrap.Bootstrap; 19 | import io.netty.channel.Channel; 20 | import io.netty.channel.ChannelFuture; 21 | import io.netty.channel.ChannelFutureListener; 22 | import io.netty.channel.ChannelInitializer; 23 | import io.netty.channel.ChannelOption; 24 | import io.netty.channel.EventLoopGroup; 25 | import io.netty.channel.group.ChannelGroup; 26 | import io.netty.channel.group.DefaultChannelGroup; 27 | import io.netty.channel.nio.NioEventLoopGroup; 28 | import io.netty.channel.socket.nio.NioSocketChannel; 29 | import io.netty.handler.codec.http.HttpClientCodec; 30 | import io.netty.handler.codec.http.HttpObjectAggregator; 31 | import io.netty.handler.codec.http.HttpRequest; 32 | import io.netty.handler.logging.LogLevel; 33 | import io.netty.handler.logging.LoggingHandler; 34 | import io.netty.handler.timeout.IdleStateHandler; 35 | import io.netty.util.concurrent.GlobalEventExecutor; 36 | 37 | import java.io.IOException; 38 | import java.net.InetSocketAddress; 39 | import java.util.Map; 40 | import java.util.Map.Entry; 41 | import java.util.concurrent.ConcurrentHashMap; 42 | import java.util.concurrent.ConcurrentMap; 43 | import java.util.concurrent.LinkedBlockingQueue; 44 | import java.util.concurrent.Semaphore; 45 | import java.util.concurrent.TimeUnit; 46 | import java.util.concurrent.TimeoutException; 47 | import java.util.logging.Level; 48 | import java.util.logging.Logger; 49 | 50 | import com.zhang.client.NettyHttpResponseFuture; 51 | import com.zhang.handler.AdditionalChannelInitializer; 52 | import com.zhang.handler.NettyChannelPoolHandler; 53 | import com.zhang.util.NettyHttpResponseFutureUtil; 54 | 55 | /** 56 | * @author xianwu.zhang 57 | */ 58 | public class NettyChannelPool { 59 | private static final Logger logger = Logger 60 | .getLogger(NettyChannelPool.class 61 | .getName()); 62 | 63 | // channel pools per route 64 | private ConcurrentMap> routeToPoolChannels; 65 | 66 | // max number of channels allow to be created per route 67 | private ConcurrentMap maxPerRoute; 68 | 69 | // max time wait for a channel return from pool 70 | private int connectTimeOutInMilliSecondes; 71 | 72 | // max idle time for a channel before close 73 | private int maxIdleTimeInMilliSecondes; 74 | 75 | private AdditionalChannelInitializer additionalChannelInitializer; 76 | 77 | /** 78 | * value is false indicates that when there is not any channel in pool and no new 79 | * channel allowed to be create based on maxPerRoute, a new channel will be forced 80 | * to create.Otherwise, a TimeoutException will be thrown 81 | * */ 82 | private boolean forbidForceConnect; 83 | 84 | // default max number of channels allow to be created per route 85 | private final static int DEFAULT_MAX_PER_ROUTE = 200; 86 | 87 | private EventLoopGroup group; 88 | 89 | private final Bootstrap clientBootstrap; 90 | 91 | private static final String COLON = ":"; 92 | 93 | /** 94 | * Create a new instance of ChannelPool 95 | * 96 | * @param maxPerRoute 97 | * max number of channels per route allowed in pool 98 | * @param connectTimeOutInMilliSecondes 99 | * max time wait for a channel return from pool 100 | * @param maxIdleTimeInMilliSecondes 101 | * max idle time for a channel before close 102 | * @param forbidForceConnect 103 | * value is false indicates that when there is not any channel in pool and no new 104 | * channel allowed to be create based on maxPerRoute, a new channel will be forced 105 | * to create.Otherwise, a TimeoutException will be thrown. The default 106 | * value is false. 107 | * @param additionalChannelInitializer 108 | * user-defined initializer 109 | * @param options 110 | * user-defined options 111 | * @param customGroup user defined {@link EventLoopGroup} 112 | */ 113 | @SuppressWarnings("unchecked") 114 | public NettyChannelPool(Map maxPerRoute, int connectTimeOutInMilliSecondes, 115 | int maxIdleTimeInMilliSecondes, boolean forbidForceConnect, 116 | AdditionalChannelInitializer additionalChannelInitializer, 117 | Map options, EventLoopGroup customGroup) { 118 | 119 | this.additionalChannelInitializer = additionalChannelInitializer; 120 | this.maxIdleTimeInMilliSecondes = maxIdleTimeInMilliSecondes; 121 | this.connectTimeOutInMilliSecondes = connectTimeOutInMilliSecondes; 122 | this.maxPerRoute = new ConcurrentHashMap(); 123 | this.routeToPoolChannels = new ConcurrentHashMap>(); 124 | this.group = null == customGroup ? new NioEventLoopGroup() : customGroup; 125 | this.forbidForceConnect = forbidForceConnect; 126 | 127 | this.clientBootstrap = new Bootstrap(); 128 | clientBootstrap.group(group).channel(NioSocketChannel.class).option( 129 | ChannelOption.SO_KEEPALIVE, true).handler(new ChannelInitializer() { 130 | @Override 131 | protected void initChannel(Channel ch) throws Exception { 132 | ch.pipeline().addLast("log", new LoggingHandler(LogLevel.INFO)); 133 | 134 | ch.pipeline().addLast(HttpClientCodec.class.getSimpleName(), new HttpClientCodec()); 135 | if (null != NettyChannelPool.this.additionalChannelInitializer) { 136 | NettyChannelPool.this.additionalChannelInitializer.initChannel(ch); 137 | } 138 | 139 | ch.pipeline().addLast(HttpObjectAggregator.class.getSimpleName(), 140 | new HttpObjectAggregator(1048576)); 141 | 142 | ch.pipeline().addLast( 143 | IdleStateHandler.class.getSimpleName(), 144 | new IdleStateHandler(0, 0, NettyChannelPool.this.maxIdleTimeInMilliSecondes, 145 | TimeUnit.MILLISECONDS)); 146 | 147 | ch.pipeline().addLast(NettyChannelPoolHandler.class.getSimpleName(), 148 | new NettyChannelPoolHandler(NettyChannelPool.this)); 149 | } 150 | 151 | }); 152 | if (null != options) { 153 | for (Entry entry : options.entrySet()) { 154 | clientBootstrap.option(entry.getKey(), entry.getValue()); 155 | } 156 | } 157 | 158 | if (null != maxPerRoute) { 159 | for (Entry entry : maxPerRoute.entrySet()) { 160 | this.maxPerRoute.put(entry.getKey(), new Semaphore(entry.getValue())); 161 | } 162 | } 163 | 164 | } 165 | 166 | /** 167 | * send http request to server specified by the route. The channel used to 168 | * send the request is obtained according to the follow rules 169 | *

170 | * 1. poll the first valid channel from pool without waiting. If no valid 171 | * channel exists, then go to step 2. 172 | * 2. create a new channel and return. If failed to create a new channel, then go to step 3. 173 | * Note: the new channel created in this step will be returned to the pool 174 | * 3. poll the first valid channel from pool within specified waiting time. If no valid 175 | * channel exists and the value of forbidForceConnect is false, then throw TimeoutException. 176 | * Otherwise,go to step 4. 177 | * 4. create a new channel and return. Note: the new channel created in this step will not be returned to the pool. 178 | *

179 | * 180 | * @param route 181 | * target server 182 | * @param request 183 | * {@link HttpRequest} 184 | * @return 185 | * @throws InterruptedException 186 | * @throws TimeoutException 187 | * @throws IOException 188 | * @throws Exception 189 | */ 190 | public NettyHttpResponseFuture sendRequest(InetSocketAddress route, final HttpRequest request) 191 | throws InterruptedException, 192 | IOException { 193 | final NettyHttpResponseFuture responseFuture = new NettyHttpResponseFuture(); 194 | if (sendRequestUsePooledChannel(route, request, responseFuture, false)) { 195 | return responseFuture; 196 | } 197 | 198 | if (sendRequestUseNewChannel(route, request, responseFuture, forbidForceConnect)) { 199 | return responseFuture; 200 | } 201 | 202 | if (sendRequestUsePooledChannel(route, request, responseFuture, true)) { 203 | return responseFuture; 204 | } 205 | 206 | throw new IOException("send request failed"); 207 | } 208 | 209 | /** 210 | * return the specified channel to pool 211 | * 212 | * @param channel 213 | */ 214 | public void returnChannel(Channel channel) { 215 | if (NettyHttpResponseFutureUtil.getForceConnect(channel)) { 216 | return; 217 | } 218 | InetSocketAddress route = (InetSocketAddress) channel.remoteAddress(); 219 | String key = getKey(route); 220 | LinkedBlockingQueue poolChannels = routeToPoolChannels.get(key); 221 | 222 | if (null != channel && channel.isActive()) { 223 | if (poolChannels.offer(channel)) { 224 | logger.log(Level.INFO, channel + "returned"); 225 | } 226 | } 227 | } 228 | 229 | /** 230 | * close all channels in the pool and shut down the eventLoopGroup 231 | * 232 | * @throws InterruptedException 233 | */ 234 | public void close() throws InterruptedException { 235 | ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); 236 | 237 | for (LinkedBlockingQueue queue : routeToPoolChannels.values()) { 238 | for (Channel channel : queue) { 239 | removeChannel(channel, null); 240 | channelGroup.add(channel); 241 | } 242 | } 243 | channelGroup.close().sync(); 244 | group.shutdownGracefully(); 245 | } 246 | 247 | /** 248 | * remove the specified channel from the pool,cancel the responseFuture 249 | * and release semaphore for the route 250 | * 251 | * @param channel 252 | */ 253 | private void removeChannel(Channel channel, Throwable cause) { 254 | 255 | InetSocketAddress route = (InetSocketAddress) channel.remoteAddress(); 256 | String key = getKey(route); 257 | 258 | NettyHttpResponseFutureUtil.cancel(channel, cause); 259 | 260 | if (!NettyHttpResponseFutureUtil.getForceConnect(channel)) { 261 | LinkedBlockingQueue poolChannels = routeToPoolChannels.get(key); 262 | if (poolChannels.remove(channel)) { 263 | logger.log(Level.INFO, channel + " removed"); 264 | } 265 | getAllowCreatePerRoute(key).release(); 266 | } 267 | } 268 | 269 | private boolean sendRequestUsePooledChannel(InetSocketAddress route, final HttpRequest request, 270 | NettyHttpResponseFuture responseFuture, 271 | boolean isWaiting) throws InterruptedException { 272 | LinkedBlockingQueue poolChannels = getPoolChannels(getKey(route)); 273 | Channel channel = poolChannels.poll(); 274 | 275 | while (null != channel && !channel.isActive()) { 276 | channel = poolChannels.poll(); 277 | } 278 | 279 | if (null == channel || !channel.isActive()) { 280 | if (!isWaiting) { 281 | return false; 282 | } 283 | channel = poolChannels.poll(connectTimeOutInMilliSecondes, TimeUnit.MILLISECONDS); 284 | if (null == channel || !channel.isActive()) { 285 | logger.log(Level.WARNING, "obtain channel from pool timeout"); 286 | return false; 287 | } 288 | } 289 | 290 | logger.log(Level.INFO, channel + " reuse"); 291 | NettyHttpResponseFutureUtil.attributeResponse(channel, responseFuture); 292 | 293 | channel.writeAndFlush(request).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); 294 | return true; 295 | } 296 | 297 | private boolean sendRequestUseNewChannel(final InetSocketAddress route, 298 | final HttpRequest request, 299 | final NettyHttpResponseFuture responseFuture, 300 | boolean forceConnect) { 301 | ChannelFuture future = createChannelFuture(route, forceConnect); 302 | if (null != future) { 303 | NettyHttpResponseFutureUtil.attributeResponse(future.channel(), responseFuture); 304 | NettyHttpResponseFutureUtil.attributeRoute(future.channel(), route); 305 | future.addListener(new ChannelFutureListener() { 306 | 307 | @Override 308 | public void operationComplete(ChannelFuture future) throws Exception { 309 | if (future.isSuccess()) { 310 | 311 | future.channel().closeFuture().addListener(new ChannelFutureListener() { 312 | 313 | @Override 314 | public void operationComplete(ChannelFuture future) throws Exception { 315 | 316 | logger.log(Level.SEVERE, future.channel() + " closed, exception: " 317 | + future.cause()); 318 | removeChannel(future.channel(), future.cause()); 319 | } 320 | 321 | }); 322 | future.channel().writeAndFlush(request).addListener(CLOSE_ON_FAILURE); 323 | } else { 324 | logger.log(Level.SEVERE, future.channel() + " connect failed, exception: " 325 | + future.cause()); 326 | 327 | NettyHttpResponseFutureUtil.cancel(future.channel(), future.cause()); 328 | if (!NettyHttpResponseFutureUtil.getForceConnect(future.channel())) { 329 | releaseCreatePerRoute(future.channel()); 330 | } 331 | } 332 | } 333 | 334 | }); 335 | return true; 336 | } 337 | return false; 338 | } 339 | 340 | public void releaseCreatePerRoute(Channel channel) { 341 | InetSocketAddress route = NettyHttpResponseFutureUtil.getRoute(channel); 342 | getAllowCreatePerRoute(getKey(route)).release(); 343 | } 344 | 345 | private Semaphore getAllowCreatePerRoute(String key) { 346 | Semaphore allowCreate = maxPerRoute.get(key); 347 | if (null == allowCreate) { 348 | Semaphore newAllowCreate = new Semaphore(DEFAULT_MAX_PER_ROUTE); 349 | allowCreate = maxPerRoute.putIfAbsent(key, newAllowCreate); 350 | if (null == allowCreate) { 351 | allowCreate = newAllowCreate; 352 | } 353 | } 354 | 355 | return allowCreate; 356 | } 357 | 358 | private LinkedBlockingQueue getPoolChannels(String route) { 359 | LinkedBlockingQueue oldPoolChannels = routeToPoolChannels.get(route); 360 | if (null == oldPoolChannels) { 361 | LinkedBlockingQueue newPoolChannels = new LinkedBlockingQueue(); 362 | oldPoolChannels = routeToPoolChannels.putIfAbsent(route, newPoolChannels); 363 | if (null == oldPoolChannels) { 364 | oldPoolChannels = newPoolChannels; 365 | } 366 | } 367 | return oldPoolChannels; 368 | } 369 | 370 | private String getKey(InetSocketAddress route) { 371 | return route.getHostName() + COLON + route.getPort(); 372 | } 373 | 374 | private ChannelFuture createChannelFuture(InetSocketAddress route, boolean forceConnect) { 375 | String key = getKey(route); 376 | 377 | Semaphore allowCreate = getAllowCreatePerRoute(key); 378 | if (allowCreate.tryAcquire()) { 379 | try { 380 | ChannelFuture connectFuture = clientBootstrap.connect(route.getHostName(), route 381 | .getPort()); 382 | return connectFuture; 383 | } catch (Exception e) { 384 | logger.log(Level.SEVERE, "connect failed", e); 385 | allowCreate.release(); 386 | } 387 | } 388 | if (forceConnect) { 389 | ChannelFuture connectFuture = clientBootstrap.connect(route.getHostName(), route 390 | .getPort()); 391 | if (null != connectFuture) { 392 | NettyHttpResponseFutureUtil.attributeForceConnect(connectFuture.channel(), 393 | forceConnect); 394 | } 395 | return connectFuture; 396 | } 397 | return null; 398 | } 399 | } 400 | -------------------------------------------------------------------------------- /src/com/zhang/util/NettyHttpRequestUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 The LightNettyClient Project 3 | * 4 | * The Light netty client Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package com.zhang.util; 17 | 18 | import io.netty.handler.codec.http.DefaultFullHttpRequest; 19 | import io.netty.handler.codec.http.HttpHeaders; 20 | import io.netty.handler.codec.http.HttpMethod; 21 | import io.netty.handler.codec.http.HttpRequest; 22 | import io.netty.handler.codec.http.HttpVersion; 23 | 24 | import java.util.Map.Entry; 25 | 26 | import com.zhang.client.NettyHttpRequest; 27 | 28 | /** 29 | * @author xianwu.zhang 30 | */ 31 | public class NettyHttpRequestUtil { 32 | 33 | public static HttpRequest create(NettyHttpRequest request, HttpMethod httpMethod) { 34 | HttpRequest httpRequest = null; 35 | if (HttpMethod.POST == httpMethod) { 36 | httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, httpMethod, request 37 | .getUri().getRawPath(), request.getContent().retain()); 38 | 39 | httpRequest.headers().set(HttpHeaders.Names.CONTENT_LENGTH, 40 | request.getContent().readableBytes()); 41 | } else { 42 | httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, httpMethod, request 43 | .getUri().getRawPath()); 44 | } 45 | for (Entry entry : request.getHeaders().entrySet()) { 46 | httpRequest.headers().set(entry.getKey(), entry.getValue()); 47 | } 48 | httpRequest.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE); 49 | httpRequest.headers().set(HttpHeaders.Names.HOST, request.getUri().getHost()); 50 | 51 | return httpRequest; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/com/zhang/util/NettyHttpResponseBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 The LightNettyClient Project 3 | * 4 | * The Light netty client Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package com.zhang.util; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.handler.codec.http.HttpResponse; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | import java.util.concurrent.atomic.AtomicBoolean; 24 | 25 | import com.zhang.client.NettyHttpResponse; 26 | 27 | /** 28 | * @author xianwu.zhang 29 | */ 30 | public class NettyHttpResponseBuilder { 31 | 32 | private volatile HttpResponse pendingResponse; 33 | 34 | private volatile List pendingContents; 35 | 36 | private volatile Throwable cause; 37 | 38 | private volatile NettyHttpResponse content; 39 | 40 | private AtomicBoolean isBuild = new AtomicBoolean(false); 41 | 42 | private volatile boolean success = false; 43 | 44 | public NettyHttpResponse build() { 45 | if (isBuild.getAndSet(true)) { 46 | return content; 47 | } 48 | NettyHttpResponse response = new NettyHttpResponse(); 49 | content = response; 50 | 51 | if (success) { 52 | response.setSuccess(true); 53 | response.setVersion(pendingResponse.getProtocolVersion()); 54 | response.setStatus(pendingResponse.getStatus()); 55 | response.setHeaders(pendingResponse.headers()); 56 | response.setContents(pendingContents); 57 | } else { 58 | response.setCause(cause); 59 | } 60 | return content; 61 | } 62 | 63 | public void addContent(ByteBuf byteBuf) { 64 | if (null == pendingContents) { 65 | pendingContents = new ArrayList(); 66 | } 67 | pendingContents.add(byteBuf); 68 | } 69 | 70 | /** 71 | * @return the contents 72 | */ 73 | public List getContents() { 74 | return pendingContents; 75 | } 76 | 77 | /** 78 | * Getter method for property pendingResponse. 79 | * 80 | * @return property value of pendingResponse 81 | */ 82 | public HttpResponse getPendingResponse() { 83 | return pendingResponse; 84 | } 85 | 86 | /** 87 | * Setter method for property pendingResponse. 88 | * 89 | * @param pendingResponse value to be assigned to property pendingResponse 90 | */ 91 | public void setPendingResponse(HttpResponse pendingResponse) { 92 | this.pendingResponse = pendingResponse; 93 | } 94 | 95 | /** 96 | * Getter method for property pendingContents. 97 | * 98 | * @return property value of pendingContents 99 | */ 100 | public List getPendingContents() { 101 | return pendingContents; 102 | } 103 | 104 | /** 105 | * Setter method for property pendingContents. 106 | * 107 | * @param pendingContents value to be assigned to property pendingContents 108 | */ 109 | public void setPendingContents(List pendingContents) { 110 | this.pendingContents = pendingContents; 111 | } 112 | 113 | /** 114 | * Getter method for property cause. 115 | * 116 | * @return property value of cause 117 | */ 118 | public Throwable getCause() { 119 | return cause; 120 | } 121 | 122 | /** 123 | * Setter method for property cause. 124 | * 125 | * @param cause value to be assigned to property cause 126 | */ 127 | public void setCause(Throwable cause) { 128 | this.cause = cause; 129 | } 130 | 131 | /** 132 | * Getter method for property success. 133 | * 134 | * @return property value of success 135 | */ 136 | public boolean isSuccess() { 137 | return success; 138 | } 139 | 140 | /** 141 | * Setter method for property success. 142 | * 143 | * @param success value to be assigned to property success 144 | */ 145 | public void setSuccess(boolean success) { 146 | this.success = success; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/com/zhang/util/NettyHttpResponseFutureUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 The LightNettyClient Project 3 | * 4 | * The Light netty client Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package com.zhang.util; 17 | 18 | import io.netty.channel.Channel; 19 | import io.netty.handler.codec.http.HttpContent; 20 | import io.netty.handler.codec.http.HttpHeaders; 21 | import io.netty.handler.codec.http.HttpResponse; 22 | import io.netty.util.AttributeKey; 23 | 24 | import java.net.InetSocketAddress; 25 | 26 | import com.zhang.client.NettyHttpResponseFuture; 27 | 28 | /** 29 | * @author xianwu.zhang 30 | */ 31 | public class NettyHttpResponseFutureUtil { 32 | 33 | private static final AttributeKey DEFAULT_ATTRIBUTE = AttributeKey 34 | .valueOf("nettyHttpResponse"); 35 | 36 | private static final AttributeKey ROUTE_ATTRIBUTE = AttributeKey 37 | .valueOf("route"); 38 | 39 | private static final AttributeKey FORCE_CONNECT_ATTRIBUTE = AttributeKey 40 | .valueOf("forceConnect"); 41 | 42 | public static void attributeForceConnect(Channel channel, boolean forceConnect) { 43 | if (forceConnect) { 44 | channel.attr(FORCE_CONNECT_ATTRIBUTE).set(true); 45 | } 46 | } 47 | 48 | public static void attributeResponse(Channel channel, NettyHttpResponseFuture responseFuture) { 49 | channel.attr(DEFAULT_ATTRIBUTE).set(responseFuture); 50 | responseFuture.setChannel(channel); 51 | } 52 | 53 | public static void attributeRoute(Channel channel, InetSocketAddress route) { 54 | channel.attr(ROUTE_ATTRIBUTE).set(route); 55 | } 56 | 57 | public static NettyHttpResponseFuture getResponse(Channel channel) { 58 | return (NettyHttpResponseFuture) channel.attr(DEFAULT_ATTRIBUTE).get(); 59 | } 60 | 61 | public static InetSocketAddress getRoute(Channel channel) { 62 | return (InetSocketAddress) channel.attr(ROUTE_ATTRIBUTE).get(); 63 | } 64 | 65 | public static boolean getForceConnect(Channel channel) { 66 | Object forceConnect = channel.attr(FORCE_CONNECT_ATTRIBUTE).get(); 67 | if (null == forceConnect) { 68 | return false; 69 | } 70 | return true; 71 | } 72 | 73 | public static void setPendingResponse(Channel channel, HttpResponse pendingResponse) { 74 | NettyHttpResponseFuture responseFuture = getResponse(channel); 75 | NettyHttpResponseBuilder responseBuilder = new NettyHttpResponseBuilder(); 76 | responseBuilder.setSuccess(true); 77 | responseBuilder.setPendingResponse(pendingResponse); 78 | responseFuture.setResponseBuilder(responseBuilder); 79 | } 80 | 81 | public static boolean headerContainConnectionClose(Channel channel) { 82 | NettyHttpResponseFuture responseFuture = getResponse(channel); 83 | return HttpHeaders.Values.CLOSE.equalsIgnoreCase(responseFuture.getResponseBuilder() 84 | .getPendingResponse().headers().get(HttpHeaders.Names.CONNECTION)); 85 | } 86 | 87 | public static void setPendingContent(Channel channel, HttpContent httpContent) { 88 | NettyHttpResponseFuture responseFuture = getResponse(channel); 89 | NettyHttpResponseBuilder responseBuilder = responseFuture.getResponseBuilder(); 90 | responseBuilder.addContent(httpContent.content().retain()); 91 | } 92 | 93 | public static boolean done(Channel channel) { 94 | NettyHttpResponseFuture responseFuture = getResponse(channel); 95 | if (null != responseFuture) { 96 | return responseFuture.done(); 97 | } 98 | 99 | return true; 100 | } 101 | 102 | public static boolean cancel(Channel channel, Throwable cause) { 103 | NettyHttpResponseFuture responseFuture = getResponse(channel); 104 | return responseFuture.cancel(cause); 105 | } 106 | } 107 | --------------------------------------------------------------------------------