User-Agent
之前学习了Okhttp3
库请求网页、API
,解析 JSON
格式的结果文本。
但是也不一定任何请求都能成功,例如我们在浏览器中输入 IP
地址详情信息查询的 API
:
https://www.done.kim/api/m/service/getIpInfo.php?ip=117.89.35.58&format=json
在浏览器中是可以看到结果的,但是用 Java
程序调用却不行,console
中虽然有结果,但实际上没有需要的内容。因为一些大型网站出于安全等各种因素考虑,会对请求进行比较严格的校验,其中一个重要的校验,是判断请求是否真的来自于一个真实的浏览器。
如果不是来自浏览器,例如 Java
程序请求,API
服务器认为不是真实的浏览器访问,就直接拒绝掉了。
判断请求是否真的来自于一个真实的浏览器,需要从 HTTP
消息头(Headers
)中取得 User-Agent
信息后,才能判断。
HTTP header
HTTP
消息头 Headers
是 HTTP
协议的一项重要内容,作用是在发起请求的时候,除了请求参数
外,可以附加更多的信息。
Headers
信息并不是写在 URL
中的,属于隐藏的数据,不能直观看到。
User-Agent
User-Agent
是存放在 Headers
中的一种数据信息。作用是在指定 URL
发送请求的时候,告诉服务端当前用户的浏览器类型、版本,甚至操作系统、CPU等非隐私的技术信息
。
服务器从 Headers
中的 User-Agent
信息获取到浏览器类型、版本等数据后,就认为是一个浏览器请求的环境了,就会给出响应。
所以只要在程序代码中,附加上 User-Agent
信息,就能允许成功了。当然,这里的 User-Agent
是模拟的。
现在模拟一个 win7 + chrome
的环境,User-Agent
的写法如下:
Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1
学以致用
模拟了一个 User-Agent
,程序中怎么用呢?实际上 Okhttp3
库已经支持 Headers
了,只需要在构建 Request
对象的时候,调用 addHeader()
方法即可:
Request request = new Request.Builder().url(url).addHeader("User-Agent", "").build();
addHeader()
方法第一个参数是名称,第二个参数是值。
Referer
上节课学习了设置 User-Agent
解决程序请求 HTTP
不成功的情况。实际情况中,还有一类更严格的检查:图片防盗链。
比如下面的图片:
我们可以看到图片无法正常显示,这是因为图片服务器做了“防盗链”。
如何防盗链呢?
因为浏览器在请求网页中的图片(或其它任何文件)时,会自动在 HTTP
消息头 Headers
中,加一个 Referer
信息,表示请求的来源(或者可以理解为图片的上级是网页)。
浏览器自动加“Referer”是业界规范,是默认的行为,不容易干预,即浏览器自动告诉图片服务器,从当前 csdn.com
请求此图片,这时图片服务器拒绝了访问,因为图片服务器的规则是不允许其它网站访问图片。
为什么直接贴网址可以访问?
因为直接拷贝网址、粘贴到浏览器地址栏,就没有“来源”了。此时不是网页中加载图片,而是浏览器直接加载图片,图片没有上级。
浏览器不是上级,浏览器是浏览网页的工具而已。换句话说,“来源”、“上级”指的是访问的具体内容。“User-Agent”就表示用什么样的浏览器工具。
图片服务器允许无 Referer 信息时访问,就可以看到图片了,也就是说,决定权仍然在“图片服务器”,不同公司的服务器规则不同,不能一概而论。浏览器只是收集一些信息提交到服务器,并不能决定是否能看到图片。
程序中的 Referer
为了模拟浏览器自动加 Referer
信息的行为,可以调用语句:
Request request = new Request.Builder().url(url).addHeader("Referer", "https://ham.youkeda.com/course/j14/0").build();
http 响应状态码是:403
时表示禁止访问此图片。
解决办法
贴到浏览器能访问是因为此图片服务器允许无 Referer
信息时访问。但也不是所有图片服务器都允许呢,况且即使同一个图片服务器,也可能修改规则,某一天突然改为必须本站内才能也未可知。为了一劳永逸的解决问题,需要把 Referer
信息设置为图片原始使用的网站。
举例:
public class ImageAsker {/*** 根据输入的url,读取页面内容并返回*/public void getContent(String url) {// okHttpClient 实例OkHttpClient okHttpClient = new OkHttpClient();// 定义一个requestRequest request = new Request.Builder().url(url).addHeader("Refer","http://photo.yupoo.com/").build();try {// 执行请求Response response = okHttpClient.newCall(request).execute();// 打印最终响应的地址String realUrl = response.request().url().toString();System.out.println("最终响应地址:" +realUrl);// 打印状态码int code = response.code();System.out.println("状态码:" + code);} catch (IOException e) {// 抓取异常System.out.println("request " + url + " error . ");e.printStackTrace();}}public static void main(String[] args) {String url = "http://photo.yupoo.com/vibius/GkRSowXr/medish.jpg";ImageAsker asker = new ImageAsker();asker.getContent(url);}
}
注意上面响应状态码的打印和最终响应的地址。
Host
之前学习了如何使用 Okhttp3
库设置 HTTP
协议的 Headers
,以此解决在调用网址、API
过程中可能出现的各种问题。
Host
表示当前请求的域名,虽然这个域名已经存在于 URL
中,但遇到复杂的场景,例如使用代理服务器、或者 URL
中不写域名而是写 IP
地址进行请求等,设置 Host
就非常有用了。
由于 Host
的作用和含义比较宽泛,不能确定各种网站的服务端会拿 Host
做什么,可以有很多作用。所以当前的学习阶段会用就可以了。
跟 User-Agent
和 Referer
一样,Host
也是属于 Headers
数据的字段之一:
Request request = new Request.Builder().url(url).addHeader("Host", "www.douban.com").build();
注意:Host
的值一定是一个域名且不带协议头。
在写程序的时候把 Headers
的三个重要信息都写全,这样可以避免很多问题。