/** * A data class representing HTTP Response * * @author Yusuke Yamamoto - yusuke at mac.com */ public class Response { private static final boolean DEBUG = Configuration.getDebug(); static Logger log = Logger.getLogger(Response.class.getName()); private static ThreadLocal<DocumentBuilder> builders = new ThreadLocal<DocumentBuilder>() { @Override protected DocumentBuilder initialValue() { try { return DocumentBuilderFactory.newInstance().newDocumentBuilder(); } catch (ParserConfigurationException ex) { throw new ExceptionInInitializerError(ex); } } }; private int statusCode; private Document responseAsDocument = null; private String responseAsString = null; private InputStream is; private HttpURLConnection con; private boolean streamConsumed = false; public Response() {} public Response(HttpURLConnection con) throws IOException { this.con = con; this.statusCode = con.getResponseCode(); if (null == (is = con.getErrorStream())) { is = con.getInputStream(); } if (null != is && "gzip".equals(con.getContentEncoding())) { // the response is gzipped is = new GZIPInputStream(is); } } // for test purpose /*package*/ Response(String content) { this.responseAsString = content; } public int getStatusCode() { return statusCode; } public String getResponseHeader(String name) { if (con != null) return con.getHeaderField(name); else return null; } /** * Returns the response stream.<br> * This method cannot be called after calling asString() or asDcoument()<br> * It is suggested to call disconnect() after consuming the stream. * * <p>Disconnects the internal HttpURLConnection silently. * * @return response body stream * @throws WeiboException * @see #disconnect() */ public InputStream asStream() { if (streamConsumed) { throw new IllegalStateException("Stream has already been consumed."); } return is; } /** * Returns the response body as string.<br> * Disconnects the internal HttpURLConnection silently. * * @return response body * @throws WeiboException */ public String asString() throws WeiboException { if (null == responseAsString) { BufferedReader br; try { InputStream stream = asStream(); if (null == stream) { return null; } br = new BufferedReader(new InputStreamReader(stream, "UTF-8")); StringBuffer buf = new StringBuffer(); String line; while (null != (line = br.readLine())) { buf.append(line).append("\n"); } this.responseAsString = buf.toString(); if (Configuration.isDalvik()) { this.responseAsString = unescape(responseAsString); } log(responseAsString); stream.close(); con.disconnect(); streamConsumed = true; } catch (NullPointerException npe) { // don't remember in which case npe can be thrown throw new WeiboException(npe.getMessage(), npe); } catch (IOException ioe) { throw new WeiboException(ioe.getMessage(), ioe); } } return responseAsString; } /** * Returns the response body as org.w3c.dom.Document.<br> * Disconnects the internal HttpURLConnection silently. * * @return response body as org.w3c.dom.Document * @throws WeiboException */ public Document asDocument() throws WeiboException { if (null == responseAsDocument) { try { // it should be faster to read the inputstream directly. // but makes it difficult to troubleshoot this.responseAsDocument = builders.get().parse(new ByteArrayInputStream(asString().getBytes("UTF-8"))); } catch (SAXException saxe) { throw new WeiboException( "The response body was not well-formed:\n" + responseAsString, saxe); } catch (IOException ioe) { throw new WeiboException("There's something with the connection.", ioe); } } return responseAsDocument; } /** * Returns the response body as sinat4j.org.json.JSONObject.<br> * Disconnects the internal HttpURLConnection silently. * * @return response body as sinat4j.org.json.JSONObject * @throws WeiboException */ public JSONObject asJSONObject() throws WeiboException { try { return new JSONObject(asString()); } catch (JSONException jsone) { throw new WeiboException(jsone.getMessage() + ":" + this.responseAsString, jsone); } } /** * Returns the response body as sinat4j.org.json.JSONArray.<br> * Disconnects the internal HttpURLConnection silently. * * @return response body as sinat4j.org.json.JSONArray * @throws WeiboException */ public JSONArray asJSONArray() throws WeiboException { try { return new JSONArray(asString()); } catch (Exception jsone) { throw new WeiboException(jsone.getMessage() + ":" + this.responseAsString, jsone); } } public InputStreamReader asReader() { try { return new InputStreamReader(is, "UTF-8"); } catch (java.io.UnsupportedEncodingException uee) { return new InputStreamReader(is); } } public void disconnect() { con.disconnect(); } private static Pattern escaped = Pattern.compile("&#([0-9]{3,5});"); /** * Unescape UTF-8 escaped characters to string. * * @author [email protected] * @param original The string to be unescaped. * @return The unescaped string */ public static String unescape(String original) { Matcher mm = escaped.matcher(original); StringBuffer unescaped = new StringBuffer(); while (mm.find()) { mm.appendReplacement(unescaped, Character.toString((char) Integer.parseInt(mm.group(1), 10))); } mm.appendTail(unescaped); return unescaped.toString(); } @Override public String toString() { if (null != responseAsString) { return responseAsString; } return "Response{" + "statusCode=" + statusCode + ", response=" + responseAsDocument + ", responseString='" + responseAsString + '\'' + ", is=" + is + ", con=" + con + '}'; } private void log(String message) { if (DEBUG) { // log.debug("[" + new java.util.Date() + "]" + message); } } private void log(String message, String message2) { if (DEBUG) { log(message + message2); } } public String getResponseAsString() { return responseAsString; } public void setResponseAsString(String responseAsString) { this.responseAsString = responseAsString; } public void setStatusCode(int statusCode) { this.statusCode = statusCode; } }
/** @author sinaWeibo */ public class HttpClient implements java.io.Serializable { private static final long serialVersionUID = -176092625883595547L; private static final int OK = 200; // OK: Success! private static final int NOT_MODIFIED = 304; // Not Modified: There was no new data to return. private static final int BAD_REQUEST = 400; // Bad Request: The request was invalid. An accompanying error message will explain why. // This is the status code will be returned during rate limiting. private static final int NOT_AUTHORIZED = 401; // Not Authorized: Authentication credentials were missing or incorrect. private static final int FORBIDDEN = 403; // Forbidden: The request is understood, but it has been refused. An accompanying error // message will explain why. private static final int NOT_FOUND = 404; // Not Found: The URI requested is invalid or the resource requested, such as a user, // does not exists. private static final int NOT_ACCEPTABLE = 406; // Not Acceptable: Returned by the Search API when an invalid format is specified in the // request. private static final int INTERNAL_SERVER_ERROR = 500; // Internal Server Error: Something is broken. Please post to the group so the Weibo // team can investigate. private static final int BAD_GATEWAY = 502; // Bad Gateway: Weibo is down or being upgraded. private static final int SERVICE_UNAVAILABLE = 503; // Service Unavailable: The Weibo servers are up, but overloaded with requests. Try again // later. The search and trend methods use this to indicate when you are being rate // limited. private String proxyHost = Configuration.getProxyHost(); private int proxyPort = Configuration.getProxyPort(); private String proxyAuthUser = Configuration.getProxyUser(); private String proxyAuthPassword = Configuration.getProxyPassword(); public String getProxyHost() { return proxyHost; } /** * Sets proxy host. System property -Dsinat4j.http.proxyHost or http.proxyHost overrides this * attribute. * * @param proxyHost */ public void setProxyHost(String proxyHost) { this.proxyHost = Configuration.getProxyHost(proxyHost); } public int getProxyPort() { return proxyPort; } /** * Sets proxy port. System property -Dsinat4j.http.proxyPort or -Dhttp.proxyPort overrides this * attribute. * * @param proxyPort */ public void setProxyPort(int proxyPort) { this.proxyPort = Configuration.getProxyPort(proxyPort); } public String getProxyAuthUser() { return proxyAuthUser; } /** * Sets proxy authentication user. System property -Dsinat4j.http.proxyUser overrides this * attribute. * * @param proxyAuthUser */ public void setProxyAuthUser(String proxyAuthUser) { this.proxyAuthUser = Configuration.getProxyUser(proxyAuthUser); } public String getProxyAuthPassword() { return proxyAuthPassword; } /** * Sets proxy authentication password. System property -Dsinat4j.http.proxyPassword overrides this * attribute. * * @param proxyAuthPassword */ public void setProxyAuthPassword(String proxyAuthPassword) { this.proxyAuthPassword = Configuration.getProxyPassword(proxyAuthPassword); } private static final boolean DEBUG = Configuration.getDebug(); static Logger log = Logger.getLogger(HttpClient.class.getName()); org.apache.commons.httpclient.HttpClient client = null; private MultiThreadedHttpConnectionManager connectionManager; private int maxSize; public HttpClient() { this(150, 30000, 30000, 1024 * 1024); } public HttpClient(int maxConPerHost, int conTimeOutMs, int soTimeOutMs, int maxSize) { connectionManager = new MultiThreadedHttpConnectionManager(); HttpConnectionManagerParams params = connectionManager.getParams(); params.setDefaultMaxConnectionsPerHost(maxConPerHost); params.setConnectionTimeout(conTimeOutMs); params.setSoTimeout(soTimeOutMs); HttpClientParams clientParams = new HttpClientParams(); // 忽略cookie 避免 Cookie rejected 警告 clientParams.setCookiePolicy(CookiePolicy.IGNORE_COOKIES); client = new org.apache.commons.httpclient.HttpClient(clientParams, connectionManager); Protocol myhttps = new Protocol("https", new MySSLSocketFactory(), 443); Protocol.registerProtocol("https", myhttps); this.maxSize = maxSize; // 支持proxy if (proxyHost != null && !proxyHost.equals("")) { client.getHostConfiguration().setProxy(proxyHost, proxyPort); client.getParams().setAuthenticationPreemptive(true); if (proxyAuthUser != null && !proxyAuthUser.equals("")) { client .getState() .setProxyCredentials( AuthScope.ANY, new UsernamePasswordCredentials(proxyAuthUser, proxyAuthPassword)); log("Proxy AuthUser: "******"Proxy AuthPassword: "******"Request:"); log("GET:" + url); if (null != params && params.length > 0) { String encodedParams = HttpClient.encodeParameters(params); if (-1 == url.indexOf("?")) { url += "?" + encodedParams; } else { url += "&" + encodedParams; } } GetMethod getmethod = new GetMethod(url); return httpRequest(getmethod, token); } public Response get(String url, PostParameter[] params, Paging paging, String token) throws WeiboException { if (null != paging) { List<PostParameter> pagingParams = new ArrayList<PostParameter>(4); if (-1 != paging.getMaxId()) { pagingParams.add(new PostParameter("max_id", String.valueOf(paging.getMaxId()))); } if (-1 != paging.getSinceId()) { pagingParams.add(new PostParameter("since_id", String.valueOf(paging.getSinceId()))); } if (-1 != paging.getPage()) { pagingParams.add(new PostParameter("page", String.valueOf(paging.getPage()))); } if (-1 != paging.getCount()) { if (-1 != url.indexOf("search")) { // search api takes "rpp" pagingParams.add(new PostParameter("rpp", String.valueOf(paging.getCount()))); } else { pagingParams.add(new PostParameter("count", String.valueOf(paging.getCount()))); } } PostParameter[] newparams = null; PostParameter[] arrayPagingParams = pagingParams.toArray(new PostParameter[pagingParams.size()]); if (null != params) { newparams = new PostParameter[params.length + pagingParams.size()]; System.arraycopy(params, 0, newparams, 0, params.length); System.arraycopy(arrayPagingParams, 0, newparams, params.length, pagingParams.size()); } else { if (0 != arrayPagingParams.length) { String encodedParams = HttpClient.encodeParameters(arrayPagingParams); if (-1 != url.indexOf("?")) { url += "&" + encodedParams; } else { url += "?" + encodedParams; } } } return get(url, newparams, token); } else { return get(url, params, token); } } /** 处理http deletemethod请求 */ public Response delete(String url, PostParameter[] params, String token) throws WeiboException { if (0 != params.length) { String encodedParams = HttpClient.encodeParameters(params); if (-1 == url.indexOf("?")) { url += "?" + encodedParams; } else { url += "&" + encodedParams; } } DeleteMethod deleteMethod = new DeleteMethod(url); return httpRequest(deleteMethod, token); } /** 处理http post请求 */ public Response post(String url, PostParameter[] params, String token) throws WeiboException { return post(url, params, true, token); } public Response post(String url, PostParameter[] params, Boolean WithTokenHeader, String token) throws WeiboException { log("Request:"); log("POST" + url); PostMethod postMethod = new PostMethod(url); for (int i = 0; i < params.length; i++) { postMethod.addParameter(params[i].getName(), params[i].getValue()); } HttpMethodParams param = postMethod.getParams(); param.setContentCharset("UTF-8"); return httpRequest(postMethod, WithTokenHeader, token); } /** 支持multipart方式上传图片 */ public Response multPartURL(String url, PostParameter[] params, ImageItem item, String token) throws WeiboException { PostMethod postMethod = new PostMethod(url); try { Part[] parts = null; if (params == null) { parts = new Part[1]; } else { parts = new Part[params.length + 1]; } if (params != null) { int i = 0; for (PostParameter entry : params) { parts[i++] = new StringPart(entry.getName(), (String) entry.getValue()); } parts[parts.length - 1] = new ByteArrayPart(item.getContent(), item.getName(), item.getContentType()); } postMethod.setRequestEntity(new MultipartRequestEntity(parts, postMethod.getParams())); return httpRequest(postMethod, token); } catch (Exception ex) { throw new WeiboException(ex.getMessage(), ex, -1); } } public Response multPartURL( String fileParamName, String url, PostParameter[] params, File file, boolean authenticated, String token) throws WeiboException { PostMethod postMethod = new PostMethod(url); try { Part[] parts = null; if (params == null) { parts = new Part[1]; } else { parts = new Part[params.length + 1]; } if (params != null) { int i = 0; for (PostParameter entry : params) { parts[i++] = new StringPart(entry.getName(), (String) entry.getValue()); } } FilePart filePart = new FilePart( fileParamName, file.getName(), file, new MimetypesFileTypeMap().getContentType(file), "UTF-8"); filePart.setTransferEncoding("binary"); parts[parts.length - 1] = filePart; postMethod.setRequestEntity(new MultipartRequestEntity(parts, postMethod.getParams())); return httpRequest(postMethod, token); } catch (Exception ex) { throw new WeiboException(ex.getMessage(), ex, -1); } } public Response httpRequest(HttpMethod method, String token) throws WeiboException { return httpRequest(method, true, token); } public Response httpRequest(HttpMethod method, Boolean WithTokenHeader, String token) throws WeiboException { InetAddress ipaddr; int responseCode = -1; try { ipaddr = InetAddress.getLocalHost(); List<Header> headers = new ArrayList<Header>(); if (WithTokenHeader) { if (token == null) { throw new IllegalStateException("Oauth2 token is not set!"); } headers.add(new Header("Authorization", "OAuth2 " + token)); headers.add(new Header("API-RemoteIP", ipaddr.getHostAddress())); client.getHostConfiguration().getParams().setParameter("http.default-headers", headers); for (Header hd : headers) { log(hd.getName() + ": " + hd.getValue()); } } method .getParams() .setParameter( HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(3, false)); client.executeMethod(method); Header[] resHeader = method.getResponseHeaders(); responseCode = method.getStatusCode(); log("Response:"); log("https StatusCode:" + String.valueOf(responseCode)); for (Header header : resHeader) { log(header.getName() + ":" + header.getValue()); } Response response = new Response(); response.setResponseAsString(method.getResponseBodyAsString()); log(response.toString() + "\n"); if (responseCode != OK) { try { throw new WeiboException( getCause(responseCode), response.asJSONObject(), method.getStatusCode()); } catch (JSONException e) { e.printStackTrace(); } } return response; } catch (IOException ioe) { throw new WeiboException(ioe.getMessage(), ioe, responseCode); } finally { method.releaseConnection(); } } /* * 对parameters进行encode处理 */ public static String encodeParameters(PostParameter[] postParams) { StringBuffer buf = new StringBuffer(); for (int j = 0; j < postParams.length; j++) { if (j != 0) { buf.append("&"); } try { buf.append(URLEncoder.encode(postParams[j].getName(), "UTF-8")) .append("=") .append(URLEncoder.encode(postParams[j].getValue(), "UTF-8")); } catch (java.io.UnsupportedEncodingException neverHappen) { } } return buf.toString(); } private static class ByteArrayPart extends PartBase { private byte[] mData; private String mName; public ByteArrayPart(byte[] data, String name, String type) throws IOException { super(name, type, "UTF-8", "binary"); mName = name; mData = data; } protected void sendData(OutputStream out) throws IOException { out.write(mData); } protected long lengthOfData() throws IOException { return mData.length; } protected void sendDispositionHeader(OutputStream out) throws IOException { super.sendDispositionHeader(out); StringBuilder buf = new StringBuilder(); buf.append("; filename=\"").append(mName).append("\""); out.write(buf.toString().getBytes()); } } private static String getCause(int statusCode) { String cause = null; switch (statusCode) { case NOT_MODIFIED: break; case BAD_REQUEST: cause = "The request was invalid. An accompanying error message will explain why. This is the status code will be returned during rate limiting."; break; case NOT_AUTHORIZED: cause = "Authentication credentials were missing or incorrect."; break; case FORBIDDEN: cause = "The request is understood, but it has been refused. An accompanying error message will explain why."; break; case NOT_FOUND: cause = "The URI requested is invalid or the resource requested, such as a user, does not exists."; break; case NOT_ACCEPTABLE: cause = "Returned by the Search API when an invalid format is specified in the request."; break; case INTERNAL_SERVER_ERROR: cause = "Something is broken. Please post to the group so the Weibo team can investigate."; break; case BAD_GATEWAY: cause = "Weibo is down or being upgraded."; break; case SERVICE_UNAVAILABLE: cause = "Service Unavailable: The Weibo servers are up, but overloaded with requests. Try again later. The search and trend methods use this to indicate when you are being rate limited."; break; default: cause = ""; } return statusCode + ":" + cause; } }