OkHttp与Cookie及Cookie的持久化

参考:OkkHttp3之Cookie管理 、 模拟登录知乎并抓取用户信息

一、OkHttp3下的Cookie的使用

①、OkHttpClient取消了setCookieHandler(CookieHandler cookieHandler);

改而使用:

setCookieJar(CookieJar cookieJar);

CookieJar是一个接口,需要自己实现CookieJar的定义。

CookieJar cookieJar = new CookieJar() {
	//存储数据		
	@Override
	public void saveFromResponse(HttpUrl arg0, List arg1) {
		// TODO Auto-generated method stub
			
	}
	//读取数据	
	@Override
	public List loadForRequest(HttpUrl arg0) {
		// TODO Auto-generated method stub
		return null;
	}
};
并且使用OkHttp的Cookie类来存储Cookie,而不使用自带的HttpCookie类。


②、OkHttp推荐使用Builder来创建OkHttpClient对象,来配置OkHttpClient

OkHttpClient.Builder builder = new OkHttpClient.Builder();
//设置超时时常
builder.connectTimeout(timeout, unit);
//设置Cookie管理器
builder.cookieJar(cookieJar);
//等等。。查看文档


二、使用OkHttpClient获取知乎关注的人的数据(昵称和简介)  非持久化

OkHttp与Cookie及Cookie的持久化_第1张图片

①、初始化CookieJar

//初始化Cookie管理器
		CookieJar cookieJar = new CookieJar() {
			//Cookie缓存区
			private final Map> cookiesMap = new HashMap>();
			@Override
			public void saveFromResponse(HttpUrl arg0, List arg1) {
				// TODO Auto-generated method stub
				//移除相同的url的Cookie
				String host = arg0.host();
				List cookiesList = cookiesMap.get(host);
				if (cookiesList != null){
					cookiesMap.remove(host);
				}
				//再重新天添加
				cookiesMap.put(host, arg1);
			}
			
			@Override
			public List loadForRequest(HttpUrl arg0) {
				// TODO Auto-generated method stub
				List cookiesList = cookiesMap.get(arg0.host());
				//注:这里不能返回null,否则会报NULLException的错误。
				//原因:当Request 连接到网络的时候,OkHttp会调用loadForRequest()
				return cookiesList != null ? cookiesList : new ArrayList();
			}
		};

②、登陆知乎

1、查看知乎的Post请求

首先通过F12打开浏览器的开发者工具(本人用的Chrome)

OkHttp与Cookie及Cookie的持久化_第2张图片
点击到红色的位置,并勾选蓝色位置的选项。

2、登陆知乎:https://www.zhihu.com/

登陆完成后,会在开发者工具发现该文件(本人是手机登陆所以是phone_num,如果是邮箱登陆是emila文件)

OkHttp与Cookie及Cookie的持久化_第3张图片

然后我们查看我们要上传的数据和上传的地址

OkHttp与Cookie及Cookie的持久化_第4张图片

这里有个_xsfr:需要在登陆的html中的form表单中查找其真正的value


本人的值是:bf284aba4cc706ebfc5ebcba1c4f97fc。  据测试,该值在同一个浏览器中提交是不会变的。

③、使用OkHttp登陆,并获取Cookie

//创建OkHttpClient
		OkHttpClient client = new OkHttpClient.Builder()
						.connectTimeout(5000, TimeUnit.MILLISECONDS)
						.cookieJar(cookieJar)
						.build();
		//创建登陆的表单
		FormBody loginBody = new FormBody.Builder()
		  			.add("_xsrf", "bf284aba4cc706ebfc5ebcba1c4f97fc")
		  			.add("password", "cay1314159")
		  			.add("captcha_type", "cn")
		  			.add("remember_me", "true")
		  			.add("phone_num", "15520762775")
		  			.build();//账号密码自己填
		//创建Request请求
		Request loginRequest = new Request.Builder()
						.url("https://www.zhihu.com/login/phone_num")
						.post(loginBody)
						.build();
		//上传
		Call loginCall = client.newCall(loginRequest);
		
		try {
			//非异步执行
			Response loginResponse = loginCall.execute();
			//测试是否登陆成功
			System.out.println(loginResponse.body().string());
			//获取返回数据的头部
			Headers headers = loginResponse.headers();
			HttpUrl loginUrl = loginRequest.url();
			//获取头部的Cookie,注意:可以通过Cooke.parseAll()来获取
			List cookies = Cookie.parseAll(loginUrl, headers);
			//防止header没有Cookie的情况
			if (cookies != null){
				//存储到Cookie管理器中
				client.cookieJar().saveFromResponse(loginUrl, cookies);//这样就将Cookie存储到缓存中了
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

④、获取关注的人

本人获取数据的地址:https://www.zhihu.com/people/chen-yan-xiang-83/followees

		//获取需要提交的CookieStr
		StringBuilder cookieStr = new StringBuilder();
		//从缓存中获取Cookie
		List cookies = client.cookieJar().loadForRequest(loginRequest.url());
		//将Cookie数据弄成一行
		for(Cookie cookie : cookies){
			cookieStr.append(cookie.name()).append("=").append(cookie.value()+";");
		}
		System.out.println(cookieStr.toString());
		//设置提交的请求
		Request attentionRequest = new Request.Builder()
							.url("https://www.zhihu.com/people/chen-yan-xiang-83/followees")
							.header("Cookie", cookieStr.toString())
							.build();
		Call attentionCall = client.newCall(attentionRequest);
		try {
			//连接网络
			Response attentionResponse = attentionCall.execute();
			if (attentionResponse.isSuccessful()){
				//获取返回的数据
				String data = attentionResponse.body().string();
				//测试
				System.out.println(data);
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

⑤、通过Jsoup获取html文件的数据

Document document = Jsoup.parse(data);
Elements attentions = document.select("div.zm-profile-card");
for(Element attention : attentions){
	System.out.println("name:"+attention.select("h2").text()+"  简介:"+attention.select("span").text());
}

⑥、完整代码

public static void main(String[]args){
		//初始化Cookie管理器
		CookieJar cookieJar = new CookieJar() {
			//Cookie缓存区
			private final Map> cookiesMap = new HashMap>();
			@Override
			public void saveFromResponse(HttpUrl arg0, List arg1) {
				// TODO Auto-generated method stub
				//移除相同的url的Cookie
				String host = arg0.host();
				List cookiesList = cookiesMap.get(host);
				if (cookiesList != null){
					cookiesMap.remove(host);
				}
				//再重新天添加
				cookiesMap.put(host, arg1);
			}
			
			@Override
			public List loadForRequest(HttpUrl arg0) {
				// TODO Auto-generated method stub
				List cookiesList = cookiesMap.get(arg0.host());
				//注:这里不能返回null,否则会报NULLException的错误。
				//原因:当Request 连接到网络的时候,OkHttp会调用loadForRequest()
				return cookiesList != null ? cookiesList : new ArrayList();
			}
		};
		//创建OkHttpClient
		OkHttpClient client = new OkHttpClient.Builder()
						.connectTimeout(5000, TimeUnit.MILLISECONDS)
						.cookieJar(cookieJar)
						.build();
		//创建登陆的表单
		FormBody loginBody = new FormBody.Builder()
		  			.add("_xsrf", "bf284aba4cc706ebfc5ebcba1c4f97fc")
		  			.add("password", "cay1314159")
		  			.add("captcha_type", "cn")
		  			.add("remember_me", "true")
		  			.add("phone_num", "15520762775")
		  			.build();//账号密码自己填
		//创建Request请求
		Request loginRequest = new Request.Builder()
						.url("https://www.zhihu.com/login/phone_num")
						.post(loginBody)
						.build();
		//上传
		Call loginCall = client.newCall(loginRequest);
		
		try {
			//非异步执行
			Response loginResponse = loginCall.execute();
			//测试是否登陆成功
			System.out.println(loginResponse.body().string());
			//获取返回数据的头部
			Headers headers = loginResponse.headers();
			HttpUrl loginUrl = loginRequest.url();
			//获取头部的Cookie,注意:可以通过Cooke.parseAll()来获取
			List cookies = Cookie.parseAll(loginUrl, headers);
			//防止header没有Cookie的情况
			if (cookies != null){
				//存储到Cookie管理器中
				client.cookieJar().saveFromResponse(loginUrl, cookies);//这样就将Cookie存储到缓存中了
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
		//获取需要提交的CookieStr
		StringBuilder cookieStr = new StringBuilder();
		//从缓存中获取Cookie
		List cookies = client.cookieJar().loadForRequest(loginRequest.url());
		//将Cookie数据弄成一行
		for(Cookie cookie : cookies){
			cookieStr.append(cookie.name()).append("=").append(cookie.value()+";");
		}
		System.out.println(cookieStr.toString());
		//设置提交的请求
		Request attentionRequest = new Request.Builder()
							.url("https://www.zhihu.com/people/chen-yan-xiang-83/followees")
							.header("Cookie", cookieStr.toString())
							.build();
		Call attentionCall = client.newCall(attentionRequest);
		try {
			//连接网络
			Response attentionResponse = attentionCall.execute();
			if (attentionResponse.isSuccessful()){
				//获取返回的数据
				String data = attentionResponse.body().string();
				//测试
				System.out.println(data);
				//解析数据
				Document document = Jsoup.parse(data);
				Elements attentions = document.select("div.zm-profile-card");
				for(Element attention : attentions){
					System.out.println("name:"+attention.select("h2").text()+"  简介:"+attention.select("span").text());
				}
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

⑦、残留的问题

有些时候登陆的时候会要求填写验证码 ,由于验证码需要手动数据,还要使用到GUI交互,所以就没写。所以改代码只能在不需要提交验证码的情况下使用。之后会用Android的方式补上

⑧、封装CookieJar

根据上面的使用情况,我们可以封装三个部分:
1、当获取到Response类的时候,我们将获取到的Cookie放入CookieJar,是可封装的
2、从CookieJar中获取Cookie,并连成一个CookieString是可以封装了。
3、设定Request请求头的Cookie是可以封装的。

将其封装在HttpEngine类中
1、第一类封装
public void receiveHeaders(Headers headers) throws IOException {
    if (client.cookieJar() == CookieJar.NO_COOKIES) return;

    List cookies = Cookie.parseAll(userRequest.url(), headers);
    if (cookies.isEmpty()) return;

    client.cookieJar().saveFromResponse(userRequest.url(), cookies);
  }
2、第二类封装
private String cookieHeader(List 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();
  }
3、第三类封装
private Request networkRequest(Request request) throws IOException {
    Request.Builder result = request.newBuilder();
    
    List cookies = client.cookieJar().loadForRequest(request.url());
    if (!cookies.isEmpty()) {
      result.header("Cookie", cookieHeader(cookies));
    }

    return result.build();
}

三、Cookie的持久化

参考android-async-http的PersistentCookieStore类与SerializableHttpCookie类

*PersistentCookieStore类:该类用的是SharePreference存储,也可以换成外置的文件存储

public class PersistentCookieStore {
    private static final String LOG_TAG = "PersistentCookieStore";
    private static final String COOKIE_PREFS = "Cookies_Prefs";

    private final Map> cookies;
    private final SharedPreferences cookiePrefs;


    public PersistentCookieStore(Context context) {
        cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, 0);
        cookies = new HashMap<>();

        //将持久化的cookies缓存到内存中 即map cookies
        Map prefsMap = cookiePrefs.getAll();
        for (Map.Entry 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());
                        }
                        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缓存到内存中 如果缓存过期 就重置此cookie
        if (!cookie.persistent()) {
            if (!cookies.containsKey(url.host())) {
                cookies.put(url.host(), new ConcurrentHashMap());
            }
            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 get(HttpUrl url) {
        ArrayList 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 getCookies() {
        ArrayList 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;
    }
}

* SerializableHttpCookie类
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();
    }
}







你可能感兴趣的:(Android)