package com.keju.maomao.internet; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.w3c.dom.Document; import org.xml.sax.SAXException; import com.keju.maomao.Configuration; import com.keju.maomao.SystemException; /** * HTTP响应类 * @author Zhoujun */ public class Response { private final static boolean DEBUG = Configuration.getDebug(); 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. * * Disconnects the internal HttpURLConnection silently. * @return response body stream * @throws SystemException * @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 SystemException */ public String asString() throws SystemException{ 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("\r\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 SystemException(npe.getMessage(), npe); } catch (IOException ioe) { throw new SystemException(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 SystemException */ public Document asDocument() throws SystemException { 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 SystemException("The response body was not well-formed:\n" + responseAsString, saxe); } catch (IOException ioe) { throw new SystemException("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 SystemException */ public JSONObject asJSONObject() throws SystemException { try { return new JSONObject(asString()); } catch (JSONException jsone) { throw new SystemException(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 SystemException */ public JSONArray asJSONArray() throws SystemException { try { return new JSONArray(asString()); } catch (Exception jsone) { throw new SystemException(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 aizhimin * @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) { System.out.println("[" + new java.util.Date() + "]" + message); } } public String getResponseAsString() { return responseAsString; } public void setResponseAsString(String responseAsString) { this.responseAsString = responseAsString; } public void setStatusCode(int statusCode) { this.statusCode = statusCode; } }