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.URI;
20  import java.net.URISyntaxException;
21  import java.util.ArrayList;
22  import java.util.Date;
23  import java.util.HashSet;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Map.Entry;
27  import java.util.Set;
28  
29  import org.slf4j.Logger;
30  import org.slf4j.LoggerFactory;
31  
32  import com.google.common.base.Strings;
33  import com.google.common.collect.Maps;
34  
35  import io.netty.buffer.ByteBuf;
36  import io.netty.buffer.Unpooled;
37  import io.netty.handler.codec.DecoderResult;
38  import io.netty.handler.codec.http.DefaultFullHttpRequest;
39  import io.netty.handler.codec.http.FullHttpRequest;
40  import io.netty.handler.codec.http.HttpHeaderNames;
41  import io.netty.handler.codec.http.HttpHeaderValues;
42  import io.netty.handler.codec.http.HttpMethod;
43  import io.netty.handler.codec.http.QueryStringDecoder;
44  import io.netty.handler.codec.http.cookie.ClientCookieEncoder;
45  import io.netty.handler.codec.http.cookie.Cookie;
46  import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
47  import io.netty.util.CharsetUtil;
48  
49  /**
50   * @author anyflow
51   */
52  public class HttpRequest extends DefaultFullHttpRequest {
53  	private static final Logger logger = LoggerFactory.getLogger(HttpRequest.class);
54  
55  	private final Map<String, List<String>> parameters;
56  	private final URI uri;
57  	private final Set<Cookie> cookies;
58  
59  	private Map<String, String> pathParameters;
60  
61  	protected HttpRequest(FullHttpRequest fullHttpRequest) throws URISyntaxException {
62  		super(fullHttpRequest.protocolVersion(), fullHttpRequest.method(), fullHttpRequest.uri(),
63  				fullHttpRequest.content().copy());
64  
65  		this.headers().set(fullHttpRequest.headers());
66  		this.trailingHeaders().set(fullHttpRequest.trailingHeaders());
67  		this.setDecoderResult(fullHttpRequest.decoderResult());
68  		this.uri = createUriWithNormalizing(fullHttpRequest.uri());
69  
70  		this.parameters = parameters();
71  		this.cookies = cookies();
72  		this.pathParameters = Maps.newHashMap();
73  	}
74  
75  	private URI createUriWithNormalizing(String uri) throws URISyntaxException {
76  		URI temp = new URI(uri);
77  
78  		String scheme = temp.getScheme();
79  		if (scheme == null) {
80  			scheme = "http";
81  		}
82  
83  		int port = temp.getPort();
84  		if (port == -1) {
85  			if (scheme.equalsIgnoreCase("http")) {
86  				port = 80;
87  			}
88  			else if (scheme.equalsIgnoreCase("https")) {
89  				port = 443;
90  			}
91  			else {
92  				throw new URISyntaxException(uri, "Invalid protocol.");
93  			}
94  		}
95  
96  		return new URI(scheme, temp.getUserInfo(), temp.getHost(), port, temp.getPath(), temp.getQuery(),
97  				temp.getFragment());
98  	}
99  
100 	public Set<Cookie> cookies() {
101 		if (cookies != null) { return cookies; }
102 
103 		String cookie = headers().get(HttpHeaderNames.COOKIE);
104 		if (cookie == null || "".equals(cookie)) { return new HashSet<Cookie>(); }
105 
106 		Set<Cookie> ret = ServerCookieDecoder.STRICT.decode(cookie);
107 
108 		return ret == null || ret.isEmpty() ? new HashSet<Cookie>() : ret;
109 	}
110 
111 	public Map<String, String> pathParameters() {
112 		return pathParameters;
113 	}
114 
115 	public HttpRequest pathParameters(Map<String, String> pathParameters) {
116 		this.pathParameters = pathParameters;
117 
118 		return this;
119 	}
120 
121 	public String pathParameter(String name) {
122 		return pathParameters.get(name);
123 	}
124 
125 	public Map<String, List<String>> parameters() {
126 
127 		if (parameters != null) { return parameters; }
128 
129 		Map<String, List<String>> ret = Maps.newHashMap();
130 
131 		if (HttpMethod.GET.equals(method()) || HttpMethod.DELETE.equals(method())) {
132 			ret.putAll(new QueryStringDecoder(uri()).parameters());
133 			return ret;
134 		}
135 		else if (headers().contains(HttpHeaderNames.CONTENT_TYPE)
136 				&& headers().get(HttpHeaderNames.CONTENT_TYPE)
137 						.startsWith(HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString())
138 				&& (HttpMethod.POST.equals(method()) || HttpMethod.PUT.equals(method()))) {
139 
140 			ret.putAll(new QueryStringDecoder("/dummy?" + content().toString(CharsetUtil.UTF_8)).parameters());
141 		}
142 
143 		return ret;
144 	}
145 
146 	/**
147 	 * Get single parameter. In case of multiple values, the method returns the
148 	 * first.
149 	 * 
150 	 * @param name
151 	 *            parameter name.
152 	 * @return The first value of the parameter name. If it does not exist, it
153 	 *         returns null.
154 	 */
155 	public String parameter(String name) {
156 		if (!parameters().containsKey(name) || parameters.get(name).size() <= 0) { return null; }
157 
158 		return parameters().get(name).get(0);
159 	}
160 
161 	public HttpRequest addParameter(String name, String value) {
162 		List<String> values = parameters().get(name);
163 		if (values == null) {
164 			values = new ArrayList<String>();
165 			values.add(value);
166 			parameters().put(name, values);
167 		}
168 		else {
169 			values.clear();
170 			values.add(value);
171 		}
172 
173 		return this;
174 	}
175 
176 	@Override
177 	public boolean equals(Object o) {
178 		return toString().equals(o.toString()); // Just for removing FindBugs
179 												// issue
180 	}
181 
182 	@Override
183 	public int hashCode() {
184 		return toString().hashCode(); // Just for removing FindBugs issue
185 	}
186 
187 	public String toString(Date incomingTime) {
188 		StringBuilder buf = new StringBuilder();
189 
190 		buf.append("\r\n");
191 		buf.append("Request URI: ").append(this.uri()).append("\r\n");
192 		buf.append("HTTP METHOD: ").append(this.method().toString()).append("\r\n");
193 		buf.append("Requested Time: ").append(incomingTime.toString()).append("\r\n");
194 		buf.append("Version: ").append(this.protocolVersion()).append("\r\n");
195 		buf.append("Request Headers:").append("\r\n");
196 
197 		List<Entry<String, String>> headers = this.headers().entries();
198 		if (!headers.isEmpty()) {
199 			for (Entry<String, String> h : this.headers().entries()) {
200 				String key = h.getKey();
201 				String value = h.getValue();
202 				buf.append("   ").append(key).append(" = ").append(value).append("\r\n");
203 			}
204 		}
205 
206 		Map<String, List<String>> params = parameters();
207 
208 		buf.append("Query String Parameters: ");
209 
210 		if (params.isEmpty()) {
211 			buf.append("NONE\r\n");
212 		}
213 		else {
214 			for (Entry<String, List<String>> p : params.entrySet()) {
215 				String key = p.getKey();
216 				List<String> vals = p.getValue();
217 				for (String val : vals) {
218 					buf.append("\r\n   ").append(key).append(" = ").append(val).append("\r\n");
219 				}
220 			}
221 		}
222 
223 		buf.append("Content: ");
224 		if (this.content().isReadable()) {
225 			buf.append("\r\n   ").append(content().toString(CharsetUtil.UTF_8));
226 		}
227 		else {
228 			buf.append("UNREADABLE CONTENT or NONE");
229 		}
230 
231 		DecoderResult result = this.decoderResult();
232 
233 		if (!result.isSuccess()) {
234 			buf.append("\r\n").append(".. WITH DECODER FAILURE:");
235 			buf.append("\r\n   ").append(result.cause());
236 		}
237 
238 		return buf.toString();
239 	}
240 
241 	@Override
242 	public String toString() {
243 		return toString(new Date());
244 	}
245 
246 	public void setContent(String content) {
247 		byte[] contentByte = Strings.nullToEmpty(content).getBytes(CharsetUtil.UTF_8);
248 
249 		headers().set(HttpHeaderNames.CONTENT_LENGTH, contentByte.length);
250 		content().writeBytes(contentByte);
251 
252 		logger.debug(content().toString(CharsetUtil.UTF_8));
253 	}
254 
255 	public URI uriObject() {
256 		return uri;
257 	}
258 
259 	protected void normalize() {
260 		normalizeParameters();
261 
262 		String encoded = ClientCookieEncoder.STRICT.encode(cookies);
263 		if (encoded == null) { return; }
264 
265 		headers().set(HttpHeaderNames.COOKIE, encoded);
266 	}
267 
268 	private String convertParametersToString() {
269 
270 		StringBuilder builder = new StringBuilder();
271 
272 		for (String name : parameters().keySet()) {
273 
274 			for (String value : parameters().get(name)) {
275 				builder = builder.append(name).append("=").append(value).append("&");
276 			}
277 		}
278 
279 		String ret = builder.toString();
280 		if (ret.length() <= 0) { return ""; }
281 
282 		if (ret.charAt(ret.length() - 1) == '&') {
283 			return ret.substring(0, ret.length() - 1);
284 		}
285 		else {
286 			return ret;
287 		}
288 	}
289 
290 	private void normalizeParameters() {
291 		String address = new StringBuilder().append(uriObject().getScheme()).append("://")
292 				.append(uriObject().getAuthority()).append(uriObject().getPath()).toString();
293 
294 		if (HttpMethod.GET.equals(method()) || HttpMethod.DELETE.equals(method())) {
295 			String parameters = convertParametersToString();
296 			address += Strings.isNullOrEmpty(parameters) ? "" : "?" + parameters;
297 		}
298 		else if ((HttpMethod.POST.equals(method()) || HttpMethod.PUT.equals(method()))
299 				&& (!headers().contains(HttpHeaderNames.CONTENT_TYPE) || headers().get(HttpHeaderNames.CONTENT_TYPE)
300 						.startsWith(HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString()))) {
301 			ByteBuf content = Unpooled.copiedBuffer(convertParametersToString(), CharsetUtil.UTF_8);
302 
303 			headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
304 			content().clear();
305 			content().writeBytes(content);
306 		}
307 
308 		setUri(address);
309 	}
310 }