1. 程式人生 > >簡單封裝 HTTP 請求

簡單封裝 HTTP 請求

2017-2-19 更新到第二版:

原始碼地址:http://git.oschina.net/sp42/ajaxjs/tree/master/ajaxjs-base/src/com/ajaxjs/net?dir=1&filepath=ajaxjs-base%2Fsrc%2Fcom%2Fajaxjs%2Fnet

和檔案操作一樣,其內部使用了鏈式風格的呼叫方式。

GET/HEAD 請求

GET 請求用法參見下面的測試用例,包括普通 GET 請求、獲取 302 重定向調轉地址、獲取資原始檔體積大小、是否 404以及下載二進位制檔案等功能。

System.out.println(Client.GET("https://www.baidu.com/"));

// 獲取資原始檔體積大小
long size = Client.getFileSize("http://c.csdnimg.cn/jifen/images/xunzhang/xunzhang/bokezhuanjiamiddle.png");
assertEquals(size, 4102L);
// 獲取 302 重定向跳轉地址
System.out.println(Client.get302redirect("https://baidu.com"));
// 封裝 head 請求檢測是否 404
assertTrue(!Client.is404("http://c.csdnimg.cn/jifen/images/xunzhang/xunzhang/bokezhuanjiamiddle.png"));
// B 站強制 Gzip 返回,無論請求是否帶有 GZIP 欄位
System.out.println(Client.GET_Gzip("http://www.bilibili.com/video/av5178498/"));
POST 請求
String url = "http://localhost:8080/pachong/post.jsp";
String result = Client.POST(url, new HashMap<String, Object>() {
    private static final long serialVersionUID = 1L;
    {
        put("foo", "bar");
    }
});
System.out.println("Feedback:" + result);


2016-7-12 : 新增 GZip 請求和 Gzip 響應判斷。

請求判斷

if(request.isEnableGzip()) // 是否啟動 GZip 請求
			connection.addRequestProperty("Accept-Encoding", "gzip, deflate"); 

一般情況下,請求頭加入了上面的 Accept-Encoding 欄位,伺服器才會對內容進行 GZip 壓縮,否則就不壓縮,原文輸出。但有些網站是不管有沒有這種請求都一概返回 GZIP 的。如果有 GZIP,伺服器會告訴我們的,在響應頭中加入 Content-Encoding 的欄位。響應判斷:

// 是否啟動 GZip 請求
			// 有些網站強制加入 Content-Encoding:gzip,而不管之前的是否有 GZip 的請求
			boolean isGzip = request.isEnableGzip() || "gzip".equals(connection.getHeaderField("Content-Encoding"));

如果不對 Gzip 的內容進行 GZipInputStream 處理會一段亂碼。

測試用例:

	@Test
	public void testGZipGet() {
		Request request = new Request();
		request.setUrl("http://u.3gtv.net");
		request.setEnableGzip(true);
		
		RequestClient rc = new RequestClient(request);
		try {
			rc.connect();
		} catch (ConnectException e) {
			System.out.println("請求出錯" + request.getUrl());
		}
		
		String html = request.getFeedback();
		System.out.println(html);
		assertNotNull(html);
	}
	// B 站強制 Gzip 返回,無論請求是否帶有 GZIP
	@Test
	public void testForce_GZipGet() {
		String url = "http://www.bilibili.com/video/av5178498/";
		String html = Get.GET(url);
		System.out.println(html);
		assertNotNull(html);
	}

-------------------------------------------------------------------------------------------------------------

簡單封裝 Java 類庫裡面的 HttpURLConnection 來完成日常的 HTTP 請求,如 GET、HEAD、POST 等的請求。

GET 請求用法參見下面的測試用例,包括普通 GET 請求、獲取 302 重定向調轉地址、獲取資原始檔體積大小、是否 404以及下載二進位制檔案等功能。

import static org.junit.Assert.*;
import org.junit.Test;

import java.io.IOException;

import com.ajaxjs.net.http.Get;
import com.ajaxjs.util.FileUtil;

public class TestGet {
	@Test
	public void testSimpleGET() {
		String url = "https://baidu.com";
		String html = Get.simpleGET(url);
		System.out.println(html);
		assertNotNull(html);
	}

	@Test
	public void testGet() {
		String url = "https://baidu.com";
		String html = Get.GET(url);
		System.out.println(html);
		assertNotNull(html);
	}

	@Test
	public void testGet302redirect() {
		String url = "http://baidu.com";
		String location = Get.get302redirect(url);
		System.out.println(location);
		assertNotNull(location);
	}

	@Test
	public void testIs404() {
		String url = "http://c.csdnimg.cn/jifen/images/xunzhang/xunzhang/bokezhuanjiamiddle.png";
		assertTrue(!Get.is404(url));
		assertTrue(Get.is404("http://www.qq.com/54543"));
	}

	@Test
	public void testGetFileSize() {
		String url = "http://c.csdnimg.cn/jifen/images/xunzhang/xunzhang/bokezhuanjiamiddle.png";
		long size = Get.getFileSize(url);
		assertEquals(size, 4102L);
	}
	
	@Test
	public void testDownload2disk() throws IOException {
		String url = "http://c.csdnimg.cn/jifen/images/xunzhang/xunzhang/bokezhuanjiamiddle.png";
		Get.download2disk(url, "c:/temp/dd.png");
		
		String saveTo = "c:/temp/js.js";
		Get.download2disk("http://bdimg.share.baidu.com/static/api/js/base/tangram.js?v=37768233.js", saveTo);
		assertNotNull(FileUtil.readFileAsText(saveTo));
	}

}

Get.simpleGET 和 Get.GET 用法一致,傳入 url 引數,返回該網址的文字內容。具體不同可能要看看原始碼。simpleGET 是原始 API 版, 一句話完事的:new URL(url).openStream(),然後把字元流轉為 text 即可。Get.GET 也是字元流轉為 text,只是前期處理上不是調 API,為自己實現邏輯,遵循了 bean 方式的呼叫。這種方式在咱們這個 HTTP 庫裡面的是通用方式。首先 Request 類是一個 Java bean,定義了請求地址 HTTP Url、請求方法 HTTP Mehtod,還有若干常見的 HTTP 頭,都做成了 getter/setter,使用者按照 Request 類的方法呼叫即可。其次 GET 請求和 POST 請求本身就差別不少,因此劃分為 Get/Post 兩個類。但實際發出請求的是 RequestClient 類。這個類是不管哪種請求的,都是圍繞後期 HTTP 響應作處理,主要是流的處理,以及一些諸如 404、超時異常的處理。下面是 RequestClient 原始碼:

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.UnknownHostException;

import com.ajaxjs.util.FileUtil;
import com.ajaxjs.util.StringUtil;

import sun.misc.BASE64Encoder;

/**
 * 發起 HTTP 請求
 * @author frank
 *
 */
public class RequestClient {

	/**
	 * 請求目標地址
	 */
	private URL url;

	/**
	 * API 連結物件
	 */
	private HttpURLConnection connection;

	/**
	 * 攜帶請求資訊的 Bean
	 */
	private Request request;

	/**
	 * 建立 HTTP 請求
	 * 
	 * @param request
	 *            請求資訊物件
	 */
	public RequestClient(Request request) {
		this.request = request;
		try {
			url = new URL(request.getUrl());
			connection = (HttpURLConnection) url.openConnection();
			connection.setRequestMethod(request.getMethod());
		} catch (IOException e) {
			System.err.println("初始化連接出錯!" + request.getUrl());
			e.printStackTrace();
		}
	}

	/**
	 * 內部初始化
	 */
	private void init() {
		connection.addRequestProperty("User-Agent", request.getUserAgent());
		connection.addRequestProperty("Referer", request.getUrl() == null ? url.getHost() : request.getUrl());
		connection.setConnectTimeout(request.getTimeout() * 1000);// 設定超時
		// connection.setReadTimeout(30000);

		if (request.getCookies() != null) {
			String cookieStr = StringUtil.HashJoin(request.getCookies(), ";");
			connection.setRequestProperty("Cookie", cookieStr);
		}

		if (request.getBasicAuthorization() != null) { // HTTP 使用者認證
			String username = request.getBasicAuthorization()[0], password = request.getBasicAuthorization()[1];
			String encoding = new BASE64Encoder().encode((username + ":" + password).getBytes());
			connection.setRequestProperty("Authorization", "Basic " + encoding);
		}
	}

	/**
	 * 發起請求
	 * 
	 * @return true 表示為發起請求成功
	 * @throws ConnectException
	 */
	public boolean connect() throws ConnectException {
		init();

		// 寫入資料(POST ONLY, GET 不需要)
		if (request.getWriteData() != null && !request.getMethod().equalsIgnoreCase("GET")) {
			// 寫入 POST 資料
			try (OutputStream os = connection.getOutputStream()) {
				os.write(request.getWriteData());
				os.flush();
			} catch (IOException e) {
				e.printStackTrace();
				return false;
			}
		}

		// 接受響應
		try (InputStream is = connection.getInputStream();) {
			if (connection.getResponseCode() >= 400) {// 如果返回的結果是400以上,那麼就說明出問題了
				ConnectException e = null;

				if (connection.getResponseCode() < 500) {
					e = new ConnectException(connection.getResponseCode() + ":客戶端請求引數錯誤!");
				} else {
					e = new ConnectException(connection.getResponseCode() + ":抱歉!我們服務端出錯了!");
				}

				String msg = FileUtil.readText(is);
				e.setFeedback(msg);

				if (request.isTextResponse())
					request.setFeedback(msg);
				throw e;
			}
			if (request.getCallback() != null) {
				request.getCallback().onDataLoad(is);
			}
			if (request.isTextResponse())
				request.setFeedback(FileUtil.readText(is));

		} catch (UnknownHostException e) {
			throw new ConnectException("未知地址!" + request.getUrl());
		} catch (FileNotFoundException e) {
			throw new ConnectException("404 地址!" + request.getUrl());
		} catch (SocketTimeoutException e) {
			throw new ConnectException("請求地址超時!" + request.getUrl());
		} catch (IOException e) {
			try {
				System.out.println(connection.getResponseCode());
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			throw new ConnectException("請求地址 IO 異常!" + request.getUrl());
		}

		return true;
	} 

	public URL getUrl() {
		return url;
	}

	public void setUrl(URL url) {
		this.url = url;
	}

	public Request getRequest() {
		return request;
	}

	public void setRequest(Request request) {
		this.request = request;
	}

	public HttpURLConnection getConnection() {
		return connection;
	}

	public void setConnection(HttpURLConnection connection) {
		this.connection = connection;
	}

}

其他原始碼就不張貼了,有興趣的可以到這裡看全部原始碼

POST 例子不多,先放一個:

String url = "http://localhost:8080/pachong/post.jsp";
Request request = Post.POST(url, new HashMap<String, Object>() {
	private static final long serialVersionUID = 1L;
	{
			put("foo", "bar");
	}
});
System.out.println("Feedback:" + request.getFeedback());
assertNotNull(request);
assertTrue(request.isDone());

程式碼比較簡單,應該沒有什麼晦澀的地方。請大家多給給意見。