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的查询部分。

 

 

 
  1. package com.example.network.http;

  2.  
  3. import java.net.MalformedURLException;

  4. import java.net.URL;

  5.  
  6. public class URLDemo {

  7.  
  8. public static void main(String[] args) throws MalformedURLException {

  9.  
  10. String str_url = "http://google.com.hk/search?q=1";

  11.  
  12. //1.把字符串封装为URL,会抛出MalformedURLException异常。

  13. URL url = new URL(str_url);

  14.  
  15. System.out.println("getProtocol:"+url.getProtocol());

  16. System.out.println("getHost:"+url.getHost());

  17. System.out.println("getPort:"+url.getPort());

  18. System.out.println("getPath:"+url.getPath());

  19. //getFile() = getPath() + getQuery

  20. System.out.println("getFile:"+url.getFile());

  21. System.out.println("getQuery:"+url.getQuery());

  22.  
  23. }

  24.  
  25. }


 

 

结果:

 

 
  1. getProtocol:http

  2. getHost:google.com.hk

  3. getPort:-1

  4. getPath:/search

  5. getFile:/search?q=1

  6. getQuery:q=1

 

 

 

 

2.3.3 URL、URI、URN区别?

 

  URI是统一资源标识符,总体来说,每一个URL都是URI,但不是每一个URI都是URL。这是因为URI还包括一个子类,即统一 资源名称(URN),它命名资源但不知道如何定位资源,比如:mailto:java-net@java.sum.com。

 

2.4 浏览器做了哪些工作?

 

2.4.1 概述

  对于TCP/IP协议而言,只需明确IP、端口号及传输的数据,而要弄清楚HTTP协议与此有什么不同,就需要看看浏览器到底给服务器发送了什么数据,那么怎么去查看呢?既然HTTP是基于TCP/IP协议,完全可以通过ServerSocket对象实现一个服务器,接收浏览器的访问,并打印出接收到的数据。

 

2.4.2 代码示例

1、服务器代码:

 

 
  1. package com.example.network.http;

  2.  
  3. import java.io.IOException;

  4. import java.io.InputStream;

  5. import java.io.OutputStream;

  6. import java.net.ServerSocket;

  7. import java.net.Socket;

  8.  
  9.  
  10. public class MyHttpServer {

  11.  
  12. public static void main(String[] args) throws IOException {

  13.  
  14. ServerSocket ss = new ServerSocket(8090);

  15.  
  16. Socket s = ss.accept();

  17.  
  18. InputStream in = s.getInputStream();

  19.  
  20. byte[] buf = new byte[1024];

  21.  
  22. int len = in.read(buf);

  23.  
  24. System.out.println(new String(buf,0,len));

  25.  
  26. OutputStream out = s.getOutputStream();

  27.  
  28. out.write("<font color=red>欢迎访问Http服务器!</font>".getBytes());

  29.  
  30. s.close();

  31. ss.close();

  32. }

  33.  
  34. }

 

 

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、提交表单。

 

 
  1. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

  2. <html>

  3. <head>

  4. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

  5. <title>登录信息</title>

  6. </head>

  7. <body>

  8. <form method="get" action="http://localhost:8090" >

  9. <table border="1">

  10. <tr><td>用户名:</td><td ><input name=name type="text"/></td></tr>

  11. <tr><td>密 码:</td><td ><input name=pass type="password"/></td></tr>

  12. <tr><th colspan="2"><input type="submit" value="登录"/></th></tr>

  13. </table>

  14. </form>

  15. </body>

  16. </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代码非常简单,如下:

 

 
  1. <html>

  2. <head>

  3. <meta charset="utf-8">

  4. </head>

  5. <body>

  6. <a href ="http://www.google.com">Goto Google</a>

  7. </body>

  8. </html>

 

 

 

2、我的浏览器代码

 

 
  1. package com.example.network.http;

  2.  
  3. import java.io.IOException;

  4. import java.io.InputStream;

  5. import java.io.PrintWriter;

  6. import java.net.Socket;

  7.  
  8. public class MyBrowser {

  9.  
  10. public static void main(String[] args) throws IOException {

  11.  
  12. //与服务器建立连接

  13. Socket s = new Socket("127.0.0.1",8080);

  14.  
  15. PrintWriter out = new PrintWriter(s.getOutputStream(),true);

  16.  
  17. //模仿浏览器向服务器发送数据,下面三行是必须的,其他消息头信息可以根据需要发送,如果不设置,会以默认值发送(未验证)。

  18. out.println("GET /myweb/mypage.html HTTP/1.1");

  19. out.println("Host: localhost:8090");

  20. out.println();

  21.  
  22.  
  23. //读取服务器返回的数据并打印

  24. InputStream in = s.getInputStream();

  25.  
  26. byte[] buf = new byte[1024];

  27.  
  28. int len = in.read(buf);

  29.  
  30. System.out.println(new String(buf,0,len));

  31.  
  32. s.close();

  33.  
  34.  
  35.  
  36. }

  37.  
  38. }

 

 

 

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。

 

代码示例:

 

 
  1. package com.example.network.http;

  2.  
  3. import java.io.IOException;

  4. import java.io.InputStream;

  5. import java.net.URL;

  6. import java.net.URLConnection;

  7.  
  8. public class URLConnectionWithGet {

  9.  
  10. public static void main(String[] args) throws IOException {

  11. //1.创建一个字符串形式的URL

  12. String str_url = "http://localhost:8080/myweb/mypage.html";

  13. //2.通过URL类的构造函数接收一个字符串形式的URL,来创建URL一个对象

  14. URL url = new URL(str_url);

  15. //3.调用openConnection()方法来返回会一个URLConnection对象,它是具体传输协议和URL的封装

  16. URLConnection conn = url.openConnection();

  17. //4.通过URLConnection来设置参数和请求属性,根据实际需要,不设置有默认值。

  18. conn.setRequestProperty("Content-Type", "text/html");

  19. //5.调用connect()方法建立与服务器的连接,或者直接调用getInputStream()方法,返回输入流,它可以读取服务器的应答体内容,此方法包含connect()的动作,所以可以跳过connect()。

  20. InputStream in = conn.getInputStream();

  21. //6.读取输入流并打印

  22. byte[] buf = new byte[1024];

  23.  
  24. int len = in.read(buf);

  25.  
  26. System.out.println(new String(buf,0,len));

  27.  
  28. in.close();

  29.  
  30. }

  31.  
  32. }

 

 

3.2 POST方式:与GET方式不同的地方在于:

 

 

1、必须要调用setDoOutput(true)方法,设置该参数,允许向服务器输入数据。

2、必须在connect()或者getInputStream()方法之前调用getOutputStream()返回输出流,并且写入数据,数据的格式为:name1=value1&name2=value2。


 

代码示例:

客户端:

 

 
  1. package com.example.network.http;

  2.  
  3. import java.io.IOException;

  4. import java.io.InputStream;

  5. import java.io.OutputStream;

  6. import java.net.URL;

  7. import java.net.URLConnection;

  8.  
  9. public class URLConnecionWithPost {

  10.  
  11. public static void main(String[] args) throws IOException {

  12.  
  13. URL url = new URL("http://localhost:8080/myweb/login.jsp");

  14.  
  15. URLConnection conn = url.openConnection();

  16.  
  17. conn.setDoOutput(true);

  18. OutputStream out = conn.getOutputStream();

  19. out.write("name=noodles&pass=26".getBytes());

  20.  
  21. InputStream in = conn.getInputStream();

  22. byte[] buf = new byte[1024];

  23. int len = in.read(buf);

  24. System.out.println(new String(buf,0,len,"utf-8"));

  25.  
  26. in.close();

  27. out.close();

  28.  
  29. }

  30.  
  31. }


 

服务端:

 

 
  1. <%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>

  2. <%

  3. request.setCharacterEncoding("UTF-8");

  4. String name = request.getParameter("name");

  5. String pass = request.getParameter("pass");

  6. if(name.equals("noodles")

  7. && pass.equals("26"))

  8. {

  9. out.println("登录成功!");

  10. }

  11. else

  12. {

  13. out.println("登录失败!");

  14. }

  15. %>

 

 

结果:

注意:这里也没有明确的指定以POST方式,但是它默认以POST的方式。

 

  补充:URLConnection还有一个子类HttpURlConnection,它是特定支持http协议的,它提供了一些额外的便捷方法,如void SetRequestMethod(String method),设置发送请求的范式;void disconnect()可以在连接空闲足够长时间后关闭。具体使用于URLConnection没有太多差别,一般就是要添加上面所说的两个方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值