在Java开发中,不可避免的需要和http打交道。而无论我司的迅雷动漫还是我主导的“搜芽”android客户端开发,都需要使用到http和服务器打交道。。虽然Java也提供了http的接口,但据我了解,更多的公司都是使用Apache的httpclient来进行开发,不仅因为它灵活强大,而且便捷。
今天,我们学习httpclient的基础知识。
关于Http的基础,在此就不再复习了。建议大家去看一本权威制作《HTTP权威指南》,加个略贵,109元人民币,不过,我买了,因为经典的书,还是多备一下,我也没怎么看,但遇到问题就翻即可。
闲话少说,切入正题。
我们发出一个http的请求,在httpclient中一般如下流程模式:
1. 创建HttpClient对象。
2. 创建请求方法的实例,并指定请求URL。如果需要发送GET请求,创建HttpGet对象;如果需要发送POST请求,创建HttpPost对象。
3. 如果需要发送请求参数,可调用HttpGet、HttpPost共同的setParams(HetpParams params)方法来添加请求参数;对于HttpPost对象而言,也可调用setEntity(HttpEntity entity)方法来设置请求参数
4. 调用HttpClient对象的execute(HttpUriRequest request)发送请求,该方法返回一个HttpResponse。
5. 调用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可获取服务器的响应头;调用HttpResponse的getEntity()方法可获取HttpEntity对象,该对象包装了服务器的响应内容。程序可通过该对象获取服务器的响应内容。
6. 释放连接。无论执行方法是否成功,都必须释放连接.
代码示例如下:
CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://localhost/"); CloseableHttpResponse response = httpclient.execute(httpget); try { <...> } finally { response.close(); }
请求:
HttpClient支持所有的HTTP/1.1的所有命令,包含:GET,HEAD,POST,PUT,DELETE,TRACE和OPTIONS。而且都有单独对应的类:HttpGet,HttpHead,....等等。
请求URI是统一资源标识符,标识了你所要请求的资源。它一般包含protocol scheme , host name, optional port, resource path,optional query ,optional fragment这些信息。如:
HttpGet httpget = new HttpGet( "http://www.google.com/search?hl=en&q=httpclient&btnG=Google+Search&aq=f&oq=");
构造上述URI一个更为好的方法是使用URIBuilder。具体如下:
URI uri = new URIBuilder() .setScheme("http") .setHost("www.google.com") .setPath("/search") .setParameter("q", "httpclient") .setParameter("btnG", "Google Search") .setParameter("aq", "f") .setParameter("oq", "") .build(); HttpGet httpget = new HttpGet(uri);
回复:
回复(response)是服务器对客户端的回复。httpclient中的回复是HttpResponse.
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); System.out.println(response.getProtocolVersion()); System.out.println(response.getStatusLine().getStatusCode()); System.out.println(response.getStatusLine().getReasonPhrase()); System.out.println(response.getStatusLine().toString());
输出:
HTTP/1.1 200 OK HTTP/1.1 200 OK
消息头部:
如果大家熟悉HTTP包的话,就知道一个HTTP报文是由三个部分组成的:对报文进行描述的起始行(start line),包含属性的首部块(header),以及可选的,包含数据的主体部分(body)。
httpclient的一个关于头部的示例:
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); response.addHeader("Set-Cookie", "c1=a; path=/; domain=localhost"); response.addHeader("Set-Cookie", "c2=b; path=\"/\", c3=c; domain=\"localhost\""); Header h1 = response.getFirstHeader("Set-Cookie"); System.out.println(h1); Header h2 = response.getLastHeader("Set-Cookie"); System.out.println(h2); Header[] hs = response.getHeaders("Set-Cookie"); System.out.println(hs.length);
其输出结果:
Set-Cookie: c1=a; path=/; domain=localhost Set-Cookie: c2=b; path="/", c3=c; domain="localhost" 2
HTTP 实体(entity):
在httpclient中存在三种实体,streamed,self-contained和wrapping。它们的区别在我们用到的时候再区分,一般而言,流对象适合接收流数据,self-contained自包含适合那些可重复读取的场景。wrapping是对已有实体的一个包装。
下面是一个使用实体的例子:
CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://localhost/"); CloseableHttpResponse response = httpclient.execute(httpget); try { HttpEntity entity = response.getEntity(); if (entity != null) { InputStream instream = entity.getContent(); try { // do something useful } finally { instream.close(); } } } finally { response.close(); }
对实体的使用:
CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://localhost/"); CloseableHttpResponse response = httpclient.execute(httpget); try { HttpEntity entity = response.getEntity(); if (entity != null) { long len = entity.getContentLength(); if (len != -1 && len < 2048) { System.out.println(EntityUtils.toString(entity)); } else { // Stream content out } } } finally { response.close(); }
那么,如何产生一个实体呢:
实体有分很多种类,所以httpclient也提供了几个产生实体的类,分别产生对应的实体。StringEntity,ByteArrayEntity,InputStreamEntity和FileEntity,它们分别产生string, byte array, input stream ,file。一个fileEntity的示例如下:
File file = new File("somefile.txt"); FileEntity entity = new FileEntity(file, ContentType.create("text/plain", "UTF-8")); HttpPost httppost = new HttpP
表单:
httpclient也提供了类似与http表单的功能,比如用户登入页面,需要用户名和密码。
List<NameValuePair> formparams = new ArrayList<NameValuePair>(); formparams.add(new BasicNameValuePair("param1", "value1")); formparams.add(new BasicNameValuePair("param2", "value2")); UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, Consts.UTF_8); HttpPost httppost = new HttpPost("http://localhost/handler.do"); httppost.setEntity(entity);
会产生如下效果:
param1=value1&m2=value2
在最后,我给出我们对httpclient的GET和POST 的方法的一个封装,这里里面的代码会将我们前面讲到的元素都涉及到。
private InputStream httpGet(String url, String cookie) { HttpGet httpGet = new HttpGet(url); httpGet.setHeader("Accept-Encoding", "gzip,deflate"); if (!(TextUtils.isEmpty(cookie))) { httpGet.setHeader("Cookie", cookie); } return httpDo(httpGet, url, null); } private InputStream httpPost(String url, Map<String, String> headers, Map<String, Object> params) { HttpPost post = new HttpPost(url); HttpEntity entity = null; Object value = params.get(POST_ENTITY); if (value instanceof HttpEntity) { entity = (HttpEntity) value; } else { List<NameValuePair> pairs = new ArrayList<NameValuePair>(); for (Map.Entry<String, Object> e : params.entrySet()) { value = e.getValue(); if (value != null) { LOG.debug("param=" + e.getKey() + ":" + value.toString()); pairs.add(new BasicNameValuePair(e.getKey(), value .toString())); } } try { entity = new UrlEncodedFormEntity(pairs, "UTF-8"); } catch (UnsupportedEncodingException e1) { LOG.warn("UnsupportedEncodingException err={}", e1.toString()); } } if (headers != null && !headers.containsKey("Content-Type")) { headers.put("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); } post.setEntity(entity); return httpDo(post, url, headers); } private InputStream httpDo(HttpUriRequest hr, String url, Map<String, String> headers) { InputStream in = null; if (headers != null) { for (String name : headers.keySet()) { hr.addHeader(name, headers.get(name)); } } DefaultHttpClient client = getClient(); HttpResponse response; try { response = client.execute(hr); int statusCode = response.getStatusLine().getStatusCode(); LOG.debug("this={}, response code={}", this, statusCode); if (statusCode == HttpStatus.SC_OK) { HttpEntity entity = response.getEntity(); if (null != entity) { Header header = entity.getContentEncoding(); if (header != null && header.getValue().equals("gzip")) { in = new GZIPInputStream(entity.getContent()); } else { in = entity.getContent(); } } } else { LOG.warn("Request HTTP resource failed. StatusCode={} Url={}", statusCode, url); } } catch (IOException e) { LOG.warn("Request HTTP resource failed. {}, err={}", this, e.toString()); } catch (IllegalStateException e) { LOG.warn("Request HTTP resource failed. url={} err={}", url, e.toString()); } return in; } private static DefaultHttpClient getClient() { HttpParams httpParams = new BasicHttpParams(); HttpConnectionParams.setConnectionTimeout(httpParams, CONNECTION_TIMEOUT); HttpConnectionParams.setSoTimeout(httpParams, SO_TIMEOUT); // http://stackoverflow.com/questions/5358014/android-httpclient-oom-on-4g-lte-htc-thunderbolt HttpConnectionParams.setSocketBufferSize(httpParams, 8192); return new DefaultHttpClient(httpParams); }
时间有限,今天到此为止。