View Javadoc
1   /*
2    * Copyright 2016 The Lannister Project
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * 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,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package net.anyflow.lannister.http;
18  
19  import java.net.URISyntaxException;
20  
21  import javax.net.ssl.TrustManagerFactory;
22  
23  import org.slf4j.Logger;
24  import org.slf4j.LoggerFactory;
25  
26  import io.netty.bootstrap.Bootstrap;
27  import io.netty.channel.Channel;
28  import io.netty.channel.ChannelFuture;
29  import io.netty.channel.ChannelFutureListener;
30  import io.netty.channel.ChannelInitializer;
31  import io.netty.channel.ChannelOption;
32  import io.netty.channel.EventLoopGroup;
33  import io.netty.channel.epoll.EpollEventLoopGroup;
34  import io.netty.channel.epoll.EpollSocketChannel;
35  import io.netty.channel.nio.NioEventLoopGroup;
36  import io.netty.channel.socket.SocketChannel;
37  import io.netty.channel.socket.nio.NioSocketChannel;
38  import io.netty.handler.codec.http.DefaultFullHttpRequest;
39  import io.netty.handler.codec.http.HttpClientCodec;
40  import io.netty.handler.codec.http.HttpContentDecompressor;
41  import io.netty.handler.codec.http.HttpHeaderNames;
42  import io.netty.handler.codec.http.HttpHeaderValues;
43  import io.netty.handler.codec.http.HttpMethod;
44  import io.netty.handler.codec.http.HttpObjectAggregator;
45  import io.netty.handler.codec.http.HttpVersion;
46  import io.netty.handler.logging.LogLevel;
47  import io.netty.handler.logging.LoggingHandler;
48  import io.netty.handler.ssl.SslContext;
49  import io.netty.handler.ssl.SslContextBuilder;
50  import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
51  import io.netty.util.concurrent.DefaultThreadFactory;
52  import net.anyflow.lannister.Literals;
53  import net.anyflow.lannister.Settings;
54  
55  public class HttpClient implements IHttpClient {
56  
57  	private static final Logger logger = LoggerFactory.getLogger(HttpClient.class);
58  
59  	private final Bootstrap bootstrap;
60  	private final HttpRequest httpRequest;
61  	private final TrustManagerFactory trustManagerFactory;
62  
63  	public HttpClient(String uri) throws UnsupportedOperationException, URISyntaxException {
64  		this(uri, false);
65  	}
66  
67  	public HttpClient(String uri, boolean useInsecureTrustManagerFactory)
68  			throws URISyntaxException, UnsupportedOperationException {
69  		trustManagerFactory = useInsecureTrustManagerFactory ? InsecureTrustManagerFactory.INSTANCE : null;
70  
71  		bootstrap = new Bootstrap();
72  
73  		httpRequest = new HttpRequest(new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri));
74  
75  		if (!httpRequest().uriObject().getScheme().equalsIgnoreCase("http")
76  				&& !httpRequest().uriObject().getScheme().equalsIgnoreCase("https")) {
77  			String message = "HTTP(S) is supported only.";
78  			logger.error(message);
79  			throw new UnsupportedOperationException(message);
80  		}
81  	}
82  
83  	@Override
84  	public HttpRequest httpRequest() {
85  		return httpRequest;
86  	}
87  
88  	@Override
89  	public HttpResponse get() {
90  		return get(null);
91  	}
92  
93  	@Override
94  	public HttpResponse get(final MessageReceiver receiver) {
95  		httpRequest().setMethod(HttpMethod.GET);
96  
97  		return request(receiver);
98  	}
99  
100 	@Override
101 	public HttpResponse post() {
102 		return post(null);
103 	}
104 
105 	@Override
106 	public HttpResponse post(final MessageReceiver receiver) {
107 		httpRequest().setMethod(HttpMethod.POST);
108 
109 		return request(receiver);
110 	}
111 
112 	@Override
113 	public HttpResponse put() {
114 		return put(null);
115 	}
116 
117 	@Override
118 	public HttpResponse put(final MessageReceiver receiver) {
119 		httpRequest().setMethod(HttpMethod.PUT);
120 
121 		return request(receiver);
122 	}
123 
124 	@Override
125 	public HttpResponse delete() {
126 		return delete(null);
127 	}
128 
129 	@Override
130 	public HttpResponse delete(final MessageReceiver receiver) {
131 		httpRequest().setMethod(HttpMethod.DELETE);
132 
133 		return request(receiver);
134 	}
135 
136 	@Override
137 	public <T> IHttpClient setOption(ChannelOption<T> option, T value) {
138 		bootstrap.option(option, value);
139 
140 		return this;
141 	}
142 
143 	private HttpResponse request(final MessageReceiver receiver) {
144 		httpRequest().normalize();
145 		setDefaultHeaders(httpRequest());
146 
147 		if (logger.isDebugEnabled()) {
148 			logger.debug(httpRequest().toString());
149 		}
150 
151 		final HttpClientHandler clientHandler = new HttpClientHandler(receiver, httpRequest);
152 
153 		EventLoopGroup group;
154 		Class<? extends SocketChannel> socketChannelClass;
155 
156 		if (Literals.NETTY_EPOLL.equals(Settings.INSTANCE.nettyTransportMode())) {
157 			group = new EpollEventLoopGroup(1, new DefaultThreadFactory("client"));
158 			socketChannelClass = EpollSocketChannel.class;
159 		}
160 		else {
161 			group = new NioEventLoopGroup(1, new DefaultThreadFactory("client"));
162 			socketChannelClass = NioSocketChannel.class;
163 		}
164 
165 		bootstrap.group(group).channel(socketChannelClass).handler(new ChannelInitializer<SocketChannel>() {
166 			@Override
167 			protected void initChannel(SocketChannel ch) throws Exception {
168 
169 				if ("true".equalsIgnoreCase(Settings.INSTANCE.getProperty("webserver.logging.writelogOfNettyLogger"))) {
170 					ch.pipeline().addLast("log", new LoggingHandler("lannister.web/server", LogLevel.DEBUG));
171 				}
172 
173 				if ("https".equalsIgnoreCase(httpRequest().uriObject().getScheme())) {
174 					SslContext sslCtx = SslContextBuilder.forClient().trustManager(trustManagerFactory).build();
175 
176 					ch.pipeline().addLast(sslCtx.newHandler(ch.alloc(), httpRequest().uriObject().getHost(),
177 							httpRequest().uriObject().getPort()));
178 				}
179 
180 				ch.pipeline().addLast("codec", new HttpClientCodec());
181 				ch.pipeline().addLast("inflater", new HttpContentDecompressor());
182 				ch.pipeline().addLast("chunkAggregator", new HttpObjectAggregator(1048576));
183 				ch.pipeline().addLast("handler", clientHandler);
184 			}
185 		});
186 
187 		try {
188 			Channel channel = bootstrap
189 					.connect(httpRequest().uriObject().getHost(), httpRequest().uriObject().getPort()).sync().channel();
190 			channel.writeAndFlush(httpRequest);
191 
192 			if (receiver == null) {
193 				channel.closeFuture().sync();
194 				group.shutdownGracefully();
195 
196 				return clientHandler.httpResponse();
197 			}
198 			else {
199 				channel.closeFuture().addListener(new ChannelFutureListener() {
200 
201 					@Override
202 					public void operationComplete(ChannelFuture future) throws Exception {
203 						group.shutdownGracefully();
204 					}
205 				});
206 
207 				return null;
208 			}
209 		}
210 		catch (Exception e) {
211 			group.shutdownGracefully();
212 			logger.error(e.getMessage(), e);
213 
214 			return null;
215 		}
216 	}
217 
218 	protected static void setDefaultHeaders(HttpRequest httpRequest) {
219 		if (!httpRequest.headers().contains(HttpHeaderNames.HOST)) {
220 			httpRequest.headers().set(HttpHeaderNames.HOST, httpRequest.uriObject().getHost());
221 		}
222 		if (!httpRequest.headers().contains(HttpHeaderNames.CONNECTION)) {
223 			httpRequest.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
224 		}
225 		if (!httpRequest.headers().contains(HttpHeaderNames.ACCEPT_ENCODING)) {
226 			httpRequest.headers().set(HttpHeaderNames.ACCEPT_ENCODING,
227 					HttpHeaderValues.GZIP + ", " + HttpHeaderValues.DEFLATE);
228 		}
229 		if (!httpRequest.headers().contains(HttpHeaderNames.ACCEPT_CHARSET)) {
230 			httpRequest.headers().set(HttpHeaderNames.ACCEPT_CHARSET, "utf-8");
231 		}
232 		if (!httpRequest.headers().contains(HttpHeaderNames.CONTENT_TYPE)) {
233 			httpRequest.headers().set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED);
234 		}
235 	}
236 }