Request.java 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. /*
  2. * Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
  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. package org.maxkey.client.http;
  17. import java.io.*;
  18. import java.net.*;
  19. import java.nio.charset.*;
  20. import java.util.*;
  21. import java.util.concurrent.*;
  22. import org.apache.commons.logging.Log;
  23. import org.apache.commons.logging.LogFactory;
  24. import org.maxkey.client.oauth.exceptions.*;
  25. import org.maxkey.client.utils.HttpsTrusts;
  26. import org.maxkey.client.utils.JsonUtils;
  27. /**
  28. * Represents an HTTP Request object
  29. *
  30. */
  31. public class Request
  32. {
  33. private static Log _log = LogFactory.getLog(Request. class );
  34. public static final String DEFAULT_CONTENT_TYPE = "application/x-www-form-urlencoded";
  35. private static final String CONTENT_LENGTH = "Content-Length";
  36. private static final String CONTENT_TYPE = "Content-Type";
  37. private static RequestTuner NOOP = new RequestTuner() {
  38. @Override public void tune(Request request){}
  39. };
  40. private String url;
  41. private HttpVerb verb;
  42. private ParameterList querystringParams;
  43. private ParameterList bodyParams;
  44. private Map<String, String> headers;
  45. private String payload = null;
  46. private HttpURLConnection connection;
  47. private String charset;
  48. private byte[] bytePayload = null;
  49. private boolean connectionKeepAlive = false;
  50. private boolean followRedirects = true;
  51. private Long connectTimeout = null;
  52. private Long readTimeout = null;
  53. private String realm;
  54. /**
  55. * Creates a new Http Request
  56. *
  57. * @param verb Http Verb (GET, POST, etc)
  58. * @param url url with optional querystring parameters.
  59. */
  60. public Request(HttpVerb verb, String url)
  61. {
  62. this.verb = verb;
  63. this.url = url;
  64. this.querystringParams = new ParameterList();
  65. this.bodyParams = new ParameterList();
  66. this.headers = new HashMap<String, String>();
  67. }
  68. /**
  69. * Execute the request and return a {@link Response}
  70. *
  71. * @return Http Response
  72. * @throws RuntimeException
  73. * if the connection cannot be created.
  74. */
  75. public Response send(RequestTuner tuner)
  76. {
  77. try
  78. {
  79. createConnection();
  80. return doSend(tuner);
  81. }
  82. catch (Exception e)
  83. {
  84. throw new OAuthConnectionException(e);
  85. }
  86. }
  87. public Response send()
  88. {
  89. return send(NOOP);
  90. }
  91. private void createConnection() throws IOException
  92. {
  93. String completeUrl = getCompleteUrl();
  94. _log.debug("verb method : "+verb);
  95. _log.debug("completeUrl : "+completeUrl);
  96. if (connection == null)
  97. {
  98. System.setProperty("http.keepAlive", connectionKeepAlive ? "true" : "false");
  99. connection = (HttpURLConnection) new URL(completeUrl).openConnection();
  100. if(completeUrl.trim().startsWith("https")){
  101. HttpsTrusts.beforeConnection();
  102. }
  103. connection.setInstanceFollowRedirects(followRedirects);
  104. }
  105. }
  106. /**
  107. * Returns the complete url (host + resource + encoded querystring parameters).
  108. *
  109. * @return the complete url.
  110. */
  111. public String getCompleteUrl()
  112. {
  113. return querystringParams.appendTo(url);
  114. }
  115. Response doSend(RequestTuner tuner) throws IOException
  116. {
  117. connection.setRequestMethod(this.verb.name());
  118. if (connectTimeout != null)
  119. {
  120. connection.setConnectTimeout(connectTimeout.intValue());
  121. }
  122. if (readTimeout != null)
  123. {
  124. connection.setReadTimeout(readTimeout.intValue());
  125. }
  126. addHeaders(connection);
  127. if (verb.equals(HttpVerb.PUT) || verb.equals(HttpVerb.POST))
  128. {
  129. addBody(connection, getByteBodyContents());
  130. }
  131. tuner.tune(this);
  132. return new Response(connection);
  133. }
  134. void addHeaders(HttpURLConnection conn)
  135. {
  136. for (String key : headers.keySet())
  137. conn.setRequestProperty(key, headers.get(key));
  138. }
  139. void addBody(HttpURLConnection conn, byte[] content) throws IOException
  140. {
  141. conn.setRequestProperty(CONTENT_LENGTH, String.valueOf(content.length));
  142. // Set default content type if none is set.
  143. if (conn.getRequestProperty(CONTENT_TYPE) == null)
  144. {
  145. conn.setRequestProperty(CONTENT_TYPE, DEFAULT_CONTENT_TYPE);
  146. }
  147. conn.setDoOutput(true);
  148. conn.getOutputStream().write(content);
  149. }
  150. /**
  151. * Add an HTTP Header to the Request
  152. *
  153. * @param key the header name
  154. * @param value the header value
  155. */
  156. public void addHeader(String key, String value)
  157. {
  158. this.headers.put(key, value);
  159. }
  160. /**
  161. * Add a body Parameter (for POST/ PUT Requests)
  162. *
  163. * @param key the parameter name
  164. * @param value the parameter value
  165. */
  166. public void addBodyParameter(String key, String value)
  167. {
  168. this.bodyParams.add(key, value);
  169. }
  170. /**
  171. * Add a QueryString parameter
  172. *
  173. * @param key the parameter name
  174. * @param value the parameter value
  175. */
  176. public void addQuerystringParameter(String key, String value)
  177. {
  178. this.querystringParams.add(key, value);
  179. }
  180. public void addParameter(String key, String value) {
  181. if (hasBodyContent()) {
  182. bodyParams.add(key, value);
  183. } else {
  184. querystringParams.add(key, value);
  185. }
  186. }
  187. protected boolean hasBodyContent() {
  188. return verb == HttpVerb.PUT || verb == HttpVerb.POST;
  189. }
  190. /**
  191. * Add body payload.
  192. *
  193. * This method is used when the HTTP body is not a form-url-encoded string,
  194. * but another thing. Like for example XML.
  195. *
  196. * Note: The contents are not part of the OAuth signature
  197. *
  198. * @param payload the body of the request
  199. */
  200. public void addPayload(String payload)
  201. {
  202. this.payload = payload;
  203. }
  204. /**
  205. * Overloaded version for byte arrays
  206. *
  207. * @param payload
  208. */
  209. public void addPayload(byte[] payload)
  210. {
  211. this.bytePayload = payload.clone();
  212. }
  213. /**
  214. * set REST Content
  215. *
  216. * @param content
  217. */
  218. public void addRestContent(String content)
  219. {
  220. this.payload = content;
  221. }
  222. /**
  223. * set REST Content
  224. *
  225. * @param content
  226. */
  227. public void addRestObject(Object content)
  228. {
  229. try {
  230. this.bytePayload = JsonUtils.gson2Json(content).getBytes("UTF-8");
  231. } catch (UnsupportedEncodingException e) {
  232. // TODO Auto-generated catch block
  233. e.printStackTrace();
  234. }
  235. }
  236. /**
  237. * set REST Content
  238. *
  239. * @param content
  240. */
  241. public void addRestContent(byte[] content)
  242. {
  243. this.bytePayload = content.clone();
  244. }
  245. /**
  246. * Get a {@link ParameterList} with the query string parameters.
  247. *
  248. * @return a {@link ParameterList} containing the query string parameters.
  249. * @throws OAuthException if the request URL is not valid.
  250. */
  251. public ParameterList getQueryStringParams()
  252. {
  253. try
  254. {
  255. ParameterList result = new ParameterList();
  256. String queryString = new URL(url).getQuery();
  257. result.addQuerystring(queryString);
  258. result.addAll(querystringParams);
  259. return result;
  260. }
  261. catch (MalformedURLException mue)
  262. {
  263. throw new OAuthException("Malformed URL", mue);
  264. }
  265. }
  266. /**
  267. * Obtains a {@link ParameterList} of the body parameters.
  268. *
  269. * @return a {@link ParameterList}containing the body parameters.
  270. */
  271. public ParameterList getBodyParams()
  272. {
  273. return bodyParams;
  274. }
  275. /**
  276. * Obtains the URL of the HTTP Request.
  277. *
  278. * @return the original URL of the HTTP Request
  279. */
  280. public String getUrl()
  281. {
  282. return url;
  283. }
  284. /**
  285. * Returns the URL without the port and the query string part.
  286. *
  287. * @return the OAuth-sanitized URL
  288. public String getSanitizedUrl()
  289. {
  290. return url.replaceAll("\\?.*", "").replace("\\:\\d{4}", "");
  291. }
  292. */
  293. /**
  294. * Returns the URL without the port and the query string part.
  295. *
  296. * @return the OAuth-sanitized URL
  297. */
  298. public String getSanitizedUrl() {
  299. if (url.startsWith("http://") && (url.endsWith(":80") || url.contains(":80/"))) {
  300. return url.replaceAll("\\?.*", "").replaceAll(":80", "");
  301. } else if (url.startsWith("https://") && (url.endsWith(":443") || url.contains(":443/"))) {
  302. return url.replaceAll("\\?.*", "").replaceAll(":443", "");
  303. } else {
  304. return url.replaceAll("\\?.*", "");
  305. }
  306. }
  307. /**
  308. * Returns the body of the request
  309. *
  310. * @return form encoded string
  311. * @throws OAuthException if the charset chosen is not supported
  312. */
  313. public String getBodyContents()
  314. {
  315. try
  316. {
  317. return new String(getByteBodyContents(),getCharset());
  318. }
  319. catch(UnsupportedEncodingException uee)
  320. {
  321. throw new OAuthException("Unsupported Charset: "+charset, uee);
  322. }
  323. }
  324. byte[] getByteBodyContents()
  325. {
  326. if (bytePayload != null) return bytePayload;
  327. String body = (payload != null) ? payload : bodyParams.asFormUrlEncodedString();
  328. _log.debug("getByteBodyContents : "+body);
  329. try
  330. {
  331. return body.getBytes(getCharset());
  332. }
  333. catch(UnsupportedEncodingException uee)
  334. {
  335. throw new OAuthException("Unsupported Charset: "+getCharset(), uee);
  336. }
  337. }
  338. /**
  339. * Returns the HTTP Verb
  340. *
  341. * @return the verb
  342. */
  343. public HttpVerb getVerb()
  344. {
  345. return verb;
  346. }
  347. public void setRealm(String realm) {
  348. this.realm = realm;
  349. }
  350. public String getRealm() {
  351. return realm;
  352. }
  353. /**
  354. * Returns the connection headers as a {@link Map}
  355. *
  356. * @return map of headers
  357. */
  358. public Map<String, String> getHeaders()
  359. {
  360. return headers;
  361. }
  362. /**
  363. * Returns the connection charset. Defaults to {@link Charset} defaultCharset if not set
  364. *
  365. * @return charset
  366. */
  367. public String getCharset()
  368. {
  369. return charset == null ? Charset.defaultCharset().name() : charset;
  370. }
  371. /**
  372. * Sets the connect timeout for the underlying {@link HttpURLConnection}
  373. *
  374. * @param duration duration of the timeout
  375. *
  376. * @param unit unit of time (milliseconds, seconds, etc)
  377. */
  378. public void setConnectTimeout(int duration, TimeUnit unit)
  379. {
  380. this.connectTimeout = unit.toMillis(duration);
  381. }
  382. /**
  383. * Sets the read timeout for the underlying {@link HttpURLConnection}
  384. *
  385. * @param duration duration of the timeout
  386. *
  387. * @param unit unit of time (milliseconds, seconds, etc)
  388. */
  389. public void setReadTimeout(int duration, TimeUnit unit)
  390. {
  391. this.readTimeout = unit.toMillis(duration);
  392. }
  393. /**
  394. * Set the charset of the body of the request
  395. *
  396. * @param charsetName name of the charset of the request
  397. */
  398. public void setCharset(String charsetName)
  399. {
  400. this.charset = charsetName;
  401. }
  402. /**
  403. * Sets whether the underlying Http Connection is persistent or not.
  404. *
  405. * @see http://download.oracle.com/javase/1.5.0/docs/guide/net/http-keepalive.html
  406. * @param connectionKeepAlive
  407. */
  408. @Deprecated
  409. public void setConnectionKeepAlive(boolean connectionKeepAlive){
  410. this.connectionKeepAlive = connectionKeepAlive;
  411. }
  412. /**
  413. *
  414. * @param connectionKeepAlive
  415. * true or false
  416. */
  417. public void setConnectionKeepAlive(String connectionKeepAlive){
  418. System.setProperty("http.keepAlive", connectionKeepAlive);
  419. }
  420. /**
  421. * Sets whether the underlying Http Connection follows redirects or not.
  422. *
  423. * Defaults to true (follow redirects)
  424. *
  425. * @see http://docs.oracle.com/javase/6/docs/api/java/net/HttpURLConnection.html#setInstanceFollowRedirects(boolean)
  426. * @param followRedirects
  427. */
  428. public void setFollowRedirects(boolean followRedirects)
  429. {
  430. this.followRedirects = followRedirects;
  431. }
  432. /*
  433. * We need this in order to stub the connection object for test cases
  434. */
  435. void setConnection(HttpURLConnection connection)
  436. {
  437. this.connection = connection;
  438. }
  439. @Override
  440. public String toString()
  441. {
  442. return String.format("@Request(%s %s)", getVerb(), getUrl());
  443. }
  444. }