Java Sokect编程之HTTP请求
1、概述
HTTP是一种协议,全称超文本传输协议,而网页就属于超文本(就是为了它服务的),可以支持多媒体等,比如图片、音频,丰富了用户的体验;它属于网络模型中应用层的协议,底层基于TCP/IP协议,并额外制定了自己的规范,所以它也是Socket编程的一种,只是由于强大和便捷的客户端和服务端软件,常常让人看不到它的本质。
首先HTTP属于网络编程,而且是基于TCP/IP协议的,那么它一定有两个Socket端点,所以具备的基本要素有:
-
- 客户端:浏览器,比如IE、Chrome、FireFox、360等
- 服务端:Web服务器,有Tomcat、Weblogic等
- 第三个往往会被忽视的:网址,专业术语URL,它包含了网络传输的基本要素:协议、IP、端口等。
2、最简单的方式实现HTTP请求。
2.1 最简单的方式是什么?
对普通用户来说,HTTP请求这个名词显得有点专业和陌生,而用“上网“或者更精确的讲”使用浏览器上网“,更能让人有恍然大悟的感觉,你可能觉得何必搞怎么一个标题呢,那只是我们没有了解到实现HTTP请求可不仅仅只有这种方式,至于还有那些方式,当了解到它的原理就自然清楚了。
即使最简单和熟悉的HTTP请求方式(上网),对软件人员来说,也需要更加专业的去分析它,通过不同的视角和见解,更加透视的看清它的内在。
2.2 流程图
通过浏览器实现HTTP请求的基本流程如下:
2.3 URL(见Java API:java.net.URL)
2.3.1 概述
Uniform Resource Locator,中文翻译,统一资源定位符,它是指向互联网"资源"的指针。资源可以是简单的文件或目 录,也可以是对更复杂对象的引用,例如对数据库或搜索引擎的查询。它的组成是有一定规则的,是信息的一种集合,一般由协议名、主机、端口、资源组成,格式如下:
protocol://host:port//resourceName,比如:http://www.crazyit.org/index.php,当然URL不仅仅支持http这一种 协议,比如https、ftp等。
有了它以后,客户端就可以对它进行解析,比如网络通信的基本要素:协议、IP、端口,都可以拿到,这样才能确定通信的目的地,而通过一个简单URL就可以对这些内容进行封装,是不是很方便呢。
2.3.2 URL常用方法:
String getProtocol(),获取URL的协议名称。
String getHost(),获取此URL的主机名。
String getPort(), 获取此URL的端口号。
String getPath(), 获取此URL的路径名称
String getFile() ,获取此URL的文件名称
String getQuery ,获取此URL的查询部分。
-
package com.example.network.http;
-
import java.net.MalformedURLException;
-
import java.net.URL;
-
public class URLDemo {
-
public static void main(String[] args) throws MalformedURLException {
-
String str_url = "http://google.com.hk/search?q=1";
-
//1.把字符串封装为URL,会抛出MalformedURLException异常。
-
URL url = new URL(str_url);
-
System.out.println("getProtocol:"+url.getProtocol());
-
System.out.println("getHost:"+url.getHost());
-
System.out.println("getPort:"+url.getPort());
-
System.out.println("getPath:"+url.getPath());
-
//getFile() = getPath() + getQuery
-
System.out.println("getFile:"+url.getFile());
-
System.out.println("getQuery:"+url.getQuery());
-
}
-
}
结果:
-
getProtocol:http
-
getHost:google.com.hk
-
getPort:-1
-
getPath:/search
-
getFile:/search?q=1
-
getQuery:q=1
2.3.3 URL、URI、URN区别?
URI是统一资源标识符,总体来说,每一个URL都是URI,但不是每一个URI都是URL。这是因为URI还包括一个子类,即统一 资源名称(URN),它命名资源但不知道如何定位资源,比如:mailto:[email protected]。
2.4 浏览器做了哪些工作?
2.4.1 概述
对于TCP/IP协议而言,只需明确IP、端口号及传输的数据,而要弄清楚HTTP协议与此有什么不同,就需要看看浏览器到底给服务器发送了什么数据,那么怎么去查看呢?既然HTTP是基于TCP/IP协议,完全可以通过ServerSocket对象实现一个服务器,接收浏览器的访问,并打印出接收到的数据。
2.4.2 代码示例
1、服务器代码:
package com.example.network.http;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class MyHttpServer {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(8090);
Socket s = ss.accept();
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
System.out.println(new String(buf,0,len));
OutputStream out = s.getOutputStream();
out.write("<font color=red>欢迎访问Http服务器!</font>".getBytes());
s.close();
ss.close();
}
}
2、在浏览器中输入服务器地址并回车。
3、查看浏览器的返回内容和服务器的控制台输出。
正如控制台中打印出来的消息,浏览器不仅仅只是简单的接收一个URL,它需要解析这个URL,并且组装出控制台中格式化数据,并且把它发给服务器,服务器接收请求并且返回应答消息,最后浏览器行使另外一个强大的功能:解析数据,如上所示我给返回字体添加了一个红色属性,最终浏览器解析并显出来。
2.4.3、发送消息的组成分析。
发送的消息分为三部分:
图中红色1表示:消息行,分为三部分:
请求方式:GET和POST。
请求的资源:遗憾的是只看到一个/,它表示无,因为我没有指定,实际它是指URL的getFile()的内容。
协议版本:目前有HTTP 1.0和HTTP 1.1。
图中红色2表示:消息头,它主要以键值对的方式显示,向服务器传递了该浏览器的一些属性,以便于服务器返回浏览器支持的数据,比如:
Accept:浏览器支持的格式,如text(文本)、HTML、XML等。
Accpet-Language:浏览器支持的语言:zh-CN,简体中文;zh 中文;en 英文等
Host:主机地址
这些内容还没有深入的了解,只是做简单的介绍。
图中红色3表示:消息体,可惜是空白,那它是什么?看完下面章节就知道了。
2.4.1 怎么向服务器发送个人数据。
不知道你发现一个问题没有,虽然浏览器给服务器发送了数据,但是这些数据都是规范化的,如果想给服务器发送点自己的数据怎么办呢?
比如想登录某个网站,必须把客户端填写的用户名和密码发送给服务器;或者在搜索引擎中查询某个关键词,也需要把这个关键词发送到服务器进行检索。
以一个简单的表单提交为例,看看浏览器到底怎么向服务器发送数据。
1、提交表单。
-
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
-
<html>
-
<head>
-
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-
<title>登录信息</title>
-
</head>
-
<body>
-
<form method="get" action="http://localhost:8090" >
-
<table border="1">
-
<tr><td>用户名:</td><td ><input name=name type="text"/></td></tr>
-
<tr><td>密 码:</td><td ><input name=pass type="password"/></td></tr>
-
<tr><th colspan="2"><input type="submit" value="登录"/></th></tr>
-
</table>
-
</form>
-
</body>
-
</html>
2、服务器代码还是上面2.5.1中的第一段代码。
3、假定在表单中填写用户名和密码。
4、提交后,查看服务器控制台内容。
你会发现最后多了一行,而这一行的内容就是你在表单中输入的用户名和密码,它以键值对的方式存储数据,并且每一组用&符号连接;另外,这一行也属于之前所说的消息体,注意消息体与消息头之间有一个空行。
2.4.2 GET和POST区别
你可能还注意到的一个细节就是在第一行中,请求方式是POST而不是GET,那么GET和POST到底有什么区别?你只需把表单以get的方式提交一次就会发现了。
首先,把第1块提交表单代码中method=“post”改为method=“get”,然后再重新运行一遍上面的流程,你会发现如下区别:
- 控制台中最后一行的消息体没有了,而第一行POST变为GET,并且请求资源变为“/?name=noodles$pass=26”,如下图:
- 第二个不易察觉的变化就是在地址栏中多了点东西,就是你的用户名和密码的信息,如下图:
所以提交个人数据的方式与请求方式密切相关,如果是POST方法,提交的数据将放置在消息体中;而GET方式,提交的数据将放置在地址栏(URL)中。
这个特点导致GET方式提交数据存在几个缺点:
1、数据不安全,假如你登录一个网站时,别人站在你后面,就可以通过地址栏查看到你用户名和密码,是不是很可怕。
2、地址栏的空间太小。
所以,如果是提交重要的数据一般都是通过POST的方式,当然不是提交数据都用POST方式,通过google或者baidu检索信息时,就是以get的方式提交数据,你可以通过地址栏查到查询的信息。
2.6 HTTP服务器向客户端返回了什么内容?
通过上面的内容,我们已经了解到客户端浏览器做了哪些工作,现在就要进一步了解服务端到底做为了什么,或者准确的讲服务器给浏览器返回了什么信息,怎么去查看呢?
这就需要我们去实现一个浏览器,然后我们模仿浏览器给服务器发送相同的消息,最后打印出服务器返回的数据。
1、你需要一个HTTP服务器,比如Tomcat,安装后,在webapp下建立一个myweb文件下,里面放置一个mypage.html文件。
mypage.html代码非常简单,如下:
-
<html>
-
<head>
-
<meta charset="utf-8">
-
</head>
-
<body>
-
<a href ="http://www.google.com">Goto Google</a>
-
</body>
-
</html>
2、我的浏览器代码
-
package com.example.network.http;
-
import java.io.IOException;
-
import java.io.InputStream;
-
import java.io.PrintWriter;
-
import java.net.Socket;
-
public class MyBrowser {
-
public static void main(String[] args) throws IOException {
-
//与服务器建立连接
-
Socket s = new Socket("127.0.0.1",8080);
-
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
-
//模仿浏览器向服务器发送数据,下面三行是必须的,其他消息头信息可以根据需要发送,如果不设置,会以默认值发送(未验证)。
-
out.println("GET /myweb/mypage.html HTTP/1.1");
-
out.println("Host: localhost:8090");
-
out.println();
-
//读取服务器返回的数据并打印
-
InputStream in = s.getInputStream();
-
byte[] buf = new byte[1024];
-
int len = in.read(buf);
-
System.out.println(new String(buf,0,len));
-
s.close();
-
}
-
}
3、执行我的浏览器代码,查看控制台信息,如下:
同样分为三部分:
图中红色1表示:应答行,分为两部分:
传输协议:HTTP1.0和HTTP1.1。
状态码:200表示成功;404表示无该页面。
图中红色2表示:应答头,因键值对的形式标识服务器或者是数据属性,如:
Server:Apache-Coyote/1.1,服务器类型。
Content-Length:125,字体长度。
Last-Modified:Wed,21 Sep 2016 15:39:17 GMT,资源的最后修改时间。
图中红色3表示:应答体,服务器返回浏览器请求的资源。
虽然我的浏览器也可以访问HTTP服务器,并且返回资源,但是与真正的浏览器还是有差距的,主要区别如下:
1、它只需要输入一个URL,就可以自动完成消息的组装并发送给服务器。
2、它可以接收数据后,一方面会解析并屏蔽掉对用户无用的应答行和应答头,另外它最强大的功能就是可以解析应答体, 并显示更加生动的页 面给用户。
3、不使用浏览器,除了通过Socket模拟浏览器的方式,还有办法发送HTTP请求嘛?
在个人的程序中,往往是不太方便去调用浏览器实现HTTP请求,即使可以也存在一个问题,就是浏览器返回的应答体,并不能被我们所利用;而通过Socket实现HTTP请求,也会出现一个问题就是它返回的IO流数据中包含了应答行和应该头,我们要自己去剔除这部分内容,保留应答体,这才是我们所需要的,有没有更好的方式呢?答案是肯定的。
3.1 请求方式GET:
1、通过字符串,构建一个URL对象。
2、调用URL的URLConnection openConnection()方法返回一个URLConnection对象,该对象是是HTTP协议和URL的封装。
3、通过URLConnection类提供的方式,设置参数或者请求属性。设置参数,如:
void setConnectTimeout(int timeOut) 连接超时。
void setReadTimeout(int timeout),读取数据超时。
void setDoOutput(boolean dooutput),连接服务器后是否可以向其传输数据,如果是需要提交数据,则需要明 确设置为true,因为默认值是false。
一般请求属性,如:
void setRequsetProperty(String key,String value)。
4、调用void connect()连接服务器。
5、调用URLConnection类提供的方式,访问应答头信息或者是返回数据。
OutputStream getInputStream(),返回输入流。
String getHeaderField(String name),返回指定的头字段值。
因为应该头信息需要经常访问,所以API中也提供了单独的方法,比如:
String getContentType(),返回
content-type
头字段的值。int getContentLength(),返回
content-length
头字段的值。long getDate(),返回
date
头字段的值。long getLastModifed,返回
last-modified
头字段的值。
注意:这里并没有明确指定请求方式为GET,因为默认是GET。
代码示例:
-
package com.example.network.http;
-
import java.io.IOException;
-
import java.io.InputStream;
-
import java.net.URL;
-
import java.net.URLConnection;
-
public class URLConnectionWithGet {
-
public static void main(String[] args) throws IOException {
-
//1.创建一个字符串形式的URL
-
String str_url = "http://localhost:8080/myweb/mypage.html";
-
//2.通过URL类的构造函数接收一个字符串形式的URL,来创建URL一个对象
-
URL url = new URL(str_url);
-
//3.调用openConnection()方法来返回会一个URLConnection对象,它是具体传输协议和URL的封装
-
URLConnection conn = url.openConnection();
-
//4.通过URLConnection来设置参数和请求属性,根据实际需要,不设置有默认值。
-
conn.setRequestProperty("Content-Type", "text/html");
-
//5.调用connect()方法建立与服务器的连接,或者直接调用getInputStream()方法,返回输入流,它可以读取服务器的应答体内容,此方法包含connect()的动作,所以可以跳过connect()。
-
InputStream in = conn.getInputStream();
-
//6.读取输入流并打印
-
byte[] buf = new byte[1024];
-
int len = in.read(buf);
-
System.out.println(new String(buf,0,len));
-
in.close();
-
}
-
}
3.2 POST方式:与GET方式不同的地方在于:
1、必须要调用setDoOutput(true)方法,设置该参数,允许向服务器输入数据。
2、必须在connect()或者getInputStream()方法之前调用getOutputStream()返回输出流,并且写入数据,数据的格式为:name1=value1&name2=value2。
代码示例:
客户端:
-
package com.example.network.http;
-
import java.io.IOException;
-
import java.io.InputStream;
-
import java.io.OutputStream;
-
import java.net.URL;
-
import java.net.URLConnection;
-
public class URLConnecionWithPost {
-
public static void main(String[] args) throws IOException {
-
URL url = new URL("http://localhost:8080/myweb/login.jsp");
-
URLConnection conn = url.openConnection();
-
conn.setDoOutput(true);
-
OutputStream out = conn.getOutputStream();
-
out.write("name=noodles&pass=26".getBytes());
-
InputStream in = conn.getInputStream();
-
byte[] buf = new byte[1024];
-
int len = in.read(buf);
-
System.out.println(new String(buf,0,len,"utf-8"));
-
in.close();
-
out.close();
-
}
-
}
服务端:
-
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
-
<%
-
request.setCharacterEncoding("UTF-8");
-
String name = request.getParameter("name");
-
String pass = request.getParameter("pass");
-
if(name.equals("noodles")
-
&& pass.equals("26"))
-
{
-
out.println("登录成功!");
-
}
-
else
-
{
-
out.println("登录失败!");
-
}
-
%>
结果:
注意:这里也没有明确的指定以POST方式,但是它默认以POST的方式。
补充:URLConnection还有一个子类HttpURlConnection,它是特定支持http协议的,它提供了一些额外的便捷方法,如void SetRequestMethod(String method),设置发送请求的范式;void disconnect()可以在连接空闲足够长时间后关闭。具体使用于URLConnection没有太多差别,一般就是要添加上面所说的两个方法。