@jimbo
2016-10-16T16:40:07.000000Z
字数 11996
阅读 890
layout: post
title: Http协议
category: 网络
NetWork
学习目标:
1、 了解http协议
2、 了解cookie的作用
3、 使用socket实现http请求
4、 说明OKhttp是如何保存cookie的
http协议,即超文本传输协议,是一种基于TCP协议,规定了服务器与浏览器之间相互通信规则的运行于应用层的数据传输协议。之所以被称为超文本传输协议,是因为http协议不仅能够传输文本,还能传输图片,视频等多媒体数据。在某些情况下,http协议还会和TSL/SSL协议一起使用,进行更加安全可靠的数据传输工作,即https。
http默认的端口号是80,https默认端口号是443

一个完整的http操作可分成下面四步:
http请求由三个部分组成:请求行,消息报头,请求正文
具体格式如下:
请求行
消息报头
请求正文
以请求方法开头+一个空格+请求的URI+一个空格+协议版本,例如:
GET /login.json HTTP/1.1
http请求方式:
请求报头允许客户端向服务端传递请求的附加信息以及客户端自身信息。
客户端可以在请求正文中添加要向服务器提交的数据。当然,这个请求要结合实体报头一起使用,请求与相应都可以添加这个实体头。
响应报文主要由状态行,响应报头和响应报文三个部分组成。
状态行由三个部分组成:协议版本+一个空格+状态码+一个空格+状态码描述
例如: HTTP/1.1 200 OK
状态代码有三位数字组成,第一个数字定义了响应的类别,且有五种可能取值:
服务器将要返回的资源内容存放于此
简单说,https协议就是一种安全版的http协议。通过TSL/SSL协议,提供客户端与服务器之间安全完整的数据传输能力。
TSL/SSL可以保证一下几点:
1. 认证用户和服务器,确保数据发送到正确的客户机和服务器
2. 加密数据以防止数据中途被窃取
3. 维护数据的完整性,确保数据在传输过程中不被改变
工作过程:
通过上面5个过程,可以保证数据发送实在正确的客户端和服务器之间进行的,使用这五个过程得到的加密密钥来加密传输数据,这样能保证传输数据的安全和完整性。
一般有两个作用:
由于http请求是无状态的,服务器无法识别请求是来自于哪一个用户,或者说哪一个客户端,所以就需要一个数据来标识身份。
package test.io;import java.net.*;import java.io.*;/*** 一个简单的Socket实现的HTTP响应服务器。* 只用于熟悉HTTP协议的目的,可以看到浏览器发过来的数据格式。** @author 赵学庆 www.java2000.net*/public class MyWebServer {public static void main(String[] args) {Socket socket = null;try {// 创建一个监听8000端口的服务器SocketServerSocket s = new ServerSocket(8000, 3);while (true) {socket = s.accept();new MyWebServerThread(socket).start();}} catch (IOException e) {e.printStackTrace();}}}class MyWebServerThread extends Thread {private Socket socket;MyWebServerThread(Socket socket) {this.socket = socket;}@Overridepublic void run() {try {InputStreamReader is = new InputStreamReader(socket.getInputStream());char[] bs = new char[2048];PrintStream out;out = new PrintStream(socket.getOutputStream());StringBuilder msg = new StringBuilder();// 如果10毫秒还没有数据,则视同没有新的数据了。// 因为有Keep-Alive的缘故,浏览器可能不主动断开连接的。// 实际应用,会根据协议第一行是GET还是 POST确定。socket.setSoTimeout(10);// 此处读入请求数据并做相应的处理//int len = -1;try {while ((len = is.read(bs)) != -1) {msg.append(bs, 0, len);msg.append("\n");}} catch (Exception ex) {// ex.printStackTrace();}// 下面是由服务器直接生成的主页内容// 1、首先向浏览器输出响应头信息out.println("HTTP/1.1 200 OK");out.println("Content-Type:text/html;charset:GBK");out.println();// 2、输出主页信息out.println(""+ ""+ "HTTP协议测试服务器,当前时间:"+ new java.util.Date()+ ""+ "username:password:"+ "username:password:"+ "phototitle:photo:"+ "您提交的数据如下:" + msg.toString() + "");out.flush();out.close();is.close();System.out.println("close");// 关闭连接socket.close();} catch (IOException e) {e.printStackTrace();}}}
class Http {HttpLine httpLine;HttpHead httpHead;HttpContent httpContent;}
在okhttp3.0中,构建request的时候可以看到如下代码private Request networkRequest(Request request) throws IOException {
Request.Builder result = request.newBuilder();
//例行省略....
List<Cookie> cookies = client.cookieJar().loadForRequest(request.url());
if (!cookies.isEmpty()) {
result.header("Cookie", cookieHeader(cookies));
}
//例行省略....
return result.build();
}
private Request networkRequest(Request request) throws IOException {Request.Builder result = request.newBuilder();//例行省略....List<Cookie> cookies = client.cookieJar().loadForRequest(request.url());if (!cookies.isEmpty()) {result.header("Cookie", cookieHeader(cookies));}//例行省略....return result.build();}private String cookieHeader(List<Cookie> cookies) {StringBuilder cookieHeader = new StringBuilder();for (int i = 0, size = cookies.size(); i < size; i++) {if (i > 0) {cookieHeader.append("; ");}Cookie cookie = cookies.get(i);cookieHeader.append(cookie.name()).append('=').append(cookie.value());}return cookieHeader.toString();}
在收到response之后还会执行如下代码:
public void receiveHeaders(Headers headers) throws IOException {if (client.cookieJar() == CookieJar.NO_COOKIES) return;List<Cookie> cookies = Cookie.parseAll(userRequest.url(), headers);if (cookies.isEmpty()) return;client.cookieJar().saveFromResponse(userRequest.url(), cookies);}
而这cookjar是一个接口:
public interface CookieJar {/** A cookie jar that never accepts any cookies. */CookieJar NO_COOKIES = new CookieJar() {@Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {}@Override public List<Cookie> loadForRequest(HttpUrl url) {return Collections.emptyList();}};/*** Saves {@code cookies} from an HTTP response to this store according to this jar's policy.** <p>Note that this method may be called a second time for a single HTTP response if the response* includes a trailer. For this obscure HTTP feature, {@code cookies} contains only the trailer's* cookies.*/void saveFromResponse(HttpUrl url, List<Cookie> cookies);/*** Load cookies from the jar for an HTTP request to {@code url}. This method returns a possibly* empty list of cookies for the network request.** <p>Simple implementations will return the accepted cookies that have not yet expired and that* {@linkplain Cookie#matches match} {@code url}.*/List<Cookie> loadForRequest(HttpUrl url);}
所以,我们在生成okhttpclient的时候添加一个实现该接口的类后就可以很轻松的管理cookie了。
OkHttpClient client = new OkHttpClient.Builder().cookieJar(new CookieJar() {private final HashMap<HttpUrl, List<Cookie>> cookieStore = new HashMap<>();@Overridepublic void saveFromResponse(HttpUrl url, List<Cookie> cookies) {cookieStore.put(url, cookies);}@Overridepublic List<Cookie> loadForRequest(HttpUrl url) {List<Cookie> cookies = cookieStore.get(url);return cookies != null ? cookies : new ArrayList<Cookie>();}}).build();
序列化cookie设计SerializableOkHttpCookies,PersistentCookieStore这两个类.
SerializableOkHttpCookies
主要做两件事:
public class SerializableOkHttpCookies implements Serializable {private transient final Cookie cookies;private transient Cookie clientCookies;public SerializableOkHttpCookies(Cookie cookies) {this.cookies = cookies;}public Cookie getCookies() {Cookie bestCookies = cookies;if (clientCookies != null) {bestCookies = clientCookies;}return bestCookies;}private void writeObject(ObjectOutputStream out) throws IOException {out.writeObject(cookies.name());out.writeObject(cookies.value());out.writeLong(cookies.expiresAt());out.writeObject(cookies.domain());out.writeObject(cookies.path());out.writeBoolean(cookies.secure());out.writeBoolean(cookies.httpOnly());out.writeBoolean(cookies.hostOnly());out.writeBoolean(cookies.persistent());}private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {String name = (String) in.readObject();String value = (String) in.readObject();long expiresAt = in.readLong();String domain = (String) in.readObject();String path = (String) in.readObject();boolean secure = in.readBoolean();boolean httpOnly = in.readBoolean();boolean hostOnly = in.readBoolean();boolean persistent = in.readBoolean();Cookie.Builder builder = new Cookie.Builder();builder = builder.name(name);builder = builder.value(value);builder = builder.expiresAt(expiresAt);builder = hostOnly ? builder.hostOnlyDomain(domain) : builder.domain(domain);builder = builder.path(path);builder = secure ? builder.secure() : builder;builder = httpOnly ? builder.httpOnly() : builder;clientCookies =builder.build();}}
PersistentCookieStore
封装管理cookie的方法
public class PersistentCookieStore {private static final String LOG_TAG = "PersistentCookieStore";private static final String COOKIE_PREFS = "Cookies_Prefs";private final Map<String, ConcurrentHashMap<String, Cookie>> cookies;private final SharedPreferences cookiePrefs;public PersistentCookieStore(Context context) {cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, 0);cookies = new HashMap<>();//将持久化的cookies缓存到内存中 即map cookiesMap<String, ?> prefsMap = cookiePrefs.getAll();for (Map.Entry<String, ?> entry : prefsMap.entrySet()) {String[] cookieNames = TextUtils.split((String) entry.getValue(), ",");for (String name : cookieNames) {String encodedCookie = cookiePrefs.getString(name, null);if (encodedCookie != null) {Cookie decodedCookie = decodeCookie(encodedCookie);if (decodedCookie != null) {if (!cookies.containsKey(entry.getKey())) {cookies.put(entry.getKey(), new ConcurrentHashMap<String, Cookie>());}cookies.get(entry.getKey()).put(name, decodedCookie);}}}}}protected String getCookieToken(Cookie cookie) {return cookie.name() + "@" + cookie.domain();}public void add(HttpUrl url, Cookie cookie) {String name = getCookieToken(cookie);//将cookies缓存到内存中 如果缓存过期 就重置此cookieif (!cookie.persistent()) {if (!cookies.containsKey(url.host())) {cookies.put(url.host(), new ConcurrentHashMap<String, Cookie>());}cookies.get(url.host()).put(name, cookie);} else {if (cookies.containsKey(url.host())) {cookies.get(url.host()).remove(name);}}//讲cookies持久化到本地SharedPreferences.Editor prefsWriter = cookiePrefs.edit();prefsWriter.putString(url.host(), TextUtils.join(",", cookies.get(url.host()).keySet()));prefsWriter.putString(name, encodeCookie(new SerializableOkHttpCookies(cookie)));prefsWriter.apply();}public List<Cookie> get(HttpUrl url) {ArrayList<Cookie> ret = new ArrayList<>();if (cookies.containsKey(url.host()))ret.addAll(cookies.get(url.host()).values());return ret;}public boolean removeAll() {SharedPreferences.Editor prefsWriter = cookiePrefs.edit();prefsWriter.clear();prefsWriter.apply();cookies.clear();return true;}public boolean remove(HttpUrl url, Cookie cookie) {String name = getCookieToken(cookie);if (cookies.containsKey(url.host()) && cookies.get(url.host()).containsKey(name)) {cookies.get(url.host()).remove(name);SharedPreferences.Editor prefsWriter = cookiePrefs.edit();if (cookiePrefs.contains(name)) {prefsWriter.remove(name);}prefsWriter.putString(url.host(), TextUtils.join(",", cookies.get(url.host()).keySet()));prefsWriter.apply();return true;} else {return false;}}public List<Cookie> getCookies() {ArrayList<Cookie> ret = new ArrayList<>();for (String key : cookies.keySet())ret.addAll(cookies.get(key).values());return ret;}/*** cookies 序列化成 string** @param cookie 要序列化的cookie* @return 序列化之后的string*/protected String encodeCookie(SerializableOkHttpCookies cookie) {if (cookie == null)return null;ByteArrayOutputStream os = new ByteArrayOutputStream();try {ObjectOutputStream outputStream = new ObjectOutputStream(os);outputStream.writeObject(cookie);} catch (IOException e) {Log.d(LOG_TAG, "IOException in encodeCookie", e);return null;}return byteArrayToHexString(os.toByteArray());}/*** 将字符串反序列化成cookies** @param cookieString cookies string* @return cookie object*/protected Cookie decodeCookie(String cookieString) {byte[] bytes = hexStringToByteArray(cookieString);ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);Cookie cookie = null;try {ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);cookie = ((SerializableOkHttpCookies) objectInputStream.readObject()).getCookies();} catch (IOException e) {Log.d(LOG_TAG, "IOException in decodeCookie", e);} catch (ClassNotFoundException e) {Log.d(LOG_TAG, "ClassNotFoundException in decodeCookie", e);}return cookie;}/*** 二进制数组转十六进制字符串** @param bytes byte array to be converted* @return string containing hex values*/protected String byteArrayToHexString(byte[] bytes) {StringBuilder sb = new StringBuilder(bytes.length * 2);for (byte element : bytes) {int v = element & 0xff;if (v < 16) {sb.append('0');}sb.append(Integer.toHexString(v));}return sb.toString().toUpperCase(Locale.US);}/*** 十六进制字符串转二进制数组** @param hexString string of hex-encoded values* @return decoded byte array*/protected byte[] hexStringToByteArray(String hexString) {int len = hexString.length();byte[] data = new byte[len / 2];for (int i = 0; i < len; i += 2) {data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i + 1), 16));}return data;}}