HTTP头部详解及使用Java套接字处理HTTP请求

HTTP头部详解及使用Java套接字处理HTTP请求

标签: javahttp服务器浏览器服务器socketurl

2008-03-18 17:39 9537人阅读 评论(1) 收藏 举报

 分类:

java(7) 

进行Web开发关键是要了解超文本传输协议(HTTP),该协议用来传输网页、图像以及因特网上在浏览器与服务器间传输的其他类型文件。只要你在浏览器上输入一个URL,最前面的http://就表示使用HTTP来访问指定位置的信息。(大部分浏览器还支持其他一些不同的协议,其中FTP就是一个典型例子。)

本文从HTTP协议的结构上初步探讨HTTP协议的工作原理和请求响应格式,并最后通过一个使用Java编写的小HTTP服务器验证了如何处理和响应HTTP请求

HTTP由两部分组成:请求和响应。当你在Web浏览器中输入一个URL时,浏览器将根据你的要求创建并发送请求,该请求包含所输入的URL以及一些与浏览器本身相关的信息。当服务器收到这个请求时将返回一个响应,该响应包括与该请求相关的信息以及位于指定URL(如果有的话)的数据。直到浏览器解析该响应并显示出网页(或其他资源)为止。

HTTP请求

HTTP请求的格式如下所示:

<request-line>

<headers>

<blank line>

[<request-body>]

在HTTP请求中,第一行必须是一个请求行(request line),用来说明请求类型、要访问的资源以及使用的HTTP版本。紧接着是一个首部(header)小节,用来说明服务器要使用的附加信息。在首部之后是一个空行,再此之后可以添加任意的其他数据[称之为主体(body)]。

在HTTP中,定义了大量的请求类型,不过Ajax开发人员关心的只有GET请求和POST请求。只要在Web浏览器上输入一个URL,浏览器就将基于该URL向服务器发送一个GET请求,以告诉服务器获取并返回什么资源。对于www.wrox.com的GET请求如下所示:

GET / HTTP/1.1

Host: www.wrox.com

User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)

Gecko/20050225 Firefox/1.0.1

Connection: Keep-Alive

请求行的第一部分说明了该请求是GET请求。该行的第二部分是一个斜杠(/),用来说明请求的是该域名的根目录。该行的最后一部分说明使用的是HTTP 1.1版本(另一个可选项是1.0)。那么请求发到哪里去呢?这就是第二行的内容。

第2行是请求的第一个首部,HOST。首部HOST将指出请求的目的地。结合HOST和上一行中的斜杠(/),可以通知服务器请求的是www.wrox.com/(HTTP 1.1才需要使用首部HOST,而原来的1.0版本则不需要使用)。第三行中包含的是首部User-Agent,服务器端和客户端脚本都能够访问它,它是浏览器类型检测逻辑的重要基础。该信息由你使用的浏览器来定义(在本例中是Firefox 1.0.1),并且在每个请求中将自动发送。最后一行是首部Connection,通常将浏览器操作设置为Keep-Alive(当然也可以设置为其他值,但这已经超出了本书讨论的范围)。注意,在最后一个首部之后有一个空行。即使不存在请求主体,这个空行也是必需的。

如果要获取一个诸如http://www.wrox.com/books的www.wrox.com域内的页面,那么该请求可能类似于:

GET /books/ HTTP/1.1

Host: www.wrox.com

User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)

Gecko/20050225 Firefox/1.0.1

Connection: Keep-Alive

注意只有第一行的内容发生了变化,它只包含URL中www.wrox.com后面的部分。

要发送GET请求的参数,则必须将这些额外的信息附在URL本身的后面。其格式类似于:

URL ? name1=value1&name2=value2&..&nameN=valueN

该信息称之为查询字符串(query string),它将会复制在HTTP请求的请求行中,如下所示:

GET /books/?name=Professional%20Ajax HTTP/1.1

Host: www.wrox.com

User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)

Gecko/20050225 Firefox/1.0.1

Connection: Keep-Alive

注意,为了将文本“Professional Ajax”作为URL的参数,需要编码处理其内容,将空格替换成%20,这称为URL编码(URL encoding),常用于HTTP的许多地方(JavaScript提供了内建的函数来处理URL编码和解码,这些将在本章中的后续部分中说明)。“名称—值”(name—value)对用 & 隔开。绝大部分的服务器端技术能够自动对请求主体进行解码,并为这些值的访问提供一些逻辑方式。当然,如何使用这些数据还是由服务器决定的。

浏览器发送的首部,通常比本文中所讨论的要多得多。为了简单起见,这里的例子尽可能简短。

另一方面,POST请求在请求主体中为服务器提供了一些附加的信息。通常,当填写一个在线表单并提交它时,这些填入的数据将以POST请求的方式发送给服务器。

以下就是一个典型的POST请求:

POST / HTTP/1.1

Host: www.wrox.com

User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)

Gecko/20050225 Firefox/1.0.1

Content-Type: application/x-www-form-urlencoded

Content-Length: 40

Connection: Keep-Alive

name=Professional%20Ajax&publisher=Wiley

从上面可以发现, POST请求和GET请求之间有一些区别。首先,请求行开始处的GET改为了POST,以表示不同的请求类型。你会发现首部Host和User-Agent仍然存在,在后面有两个新行。其中首部Content-Type说明了请求主体的内容是如何编码的。浏览器始终以application/ x-www-form- urlencoded的格式编码来传送数据,这是针对简单URL编码的MIME类型。首部Content-Length说明了请求主体的字节数。在首部Connection后是一个空行,再后面就是请求主体。与大多数浏览器的POST请求一样,这是以简单的“名称—值”对的形式给出的,其中name是Professional Ajax,publisher是Wiley。你可以以同样的格式来组织URL的查询字符串参数。

正如前面所提到的,还有其他的HTTP请求类型,它们遵从的基本格式与GET请求和POST请求相同。下一步我们来看看服务器将对HTTP请求发送什么响应。

HTTP响应

如下所示,HTTP响应的格式与请求的格式十分类似:

<status-line>

<headers>

<blank line>

[<response-body>]

正如你所见,在响应中唯一真正的区别在于第一行中用状态信息代替了请求信息。状态行(status line)通过提供一个状态码来说明所请求的资源情况。以下就是一个HTTP响应的例子:

HTTP/1.1 200 OK

Date: Sat, 31 Dec 2005 23:59:59 GMT

Content-Type: text/html;charset=ISO-8859-1

Content-Length: 122

<html>

<head>

<title>Wrox Homepage</title>

</head>

<body>

<!-- body goes here -->

</body>

</html>

在本例中,状态行给出的HTTP状态代码是200,以及消息OK。状态行始终包含的是状态码和相应的简短消息,以避免混乱。最常用的状态码有:

◆200 (OK): 找到了该资源,并且一切正常。

◆304 (NOT MODIFIED): 该资源在上次请求之后没有任何修改。这通常用于浏览器的缓存机制。

◆401 (UNAUTHORIZED): 客户端无权访问该资源。这通常会使得浏览器要求用户输入用户名和密码,以登录到服务器。

◆403 (FORBIDDEN): 客户端未能获得授权。这通常是在401之后输入了不正确的用户名或密码。

◆404 (NOT FOUND): 在指定的位置不存在所申请的资源。

在状态行之后是一些首部。通常,服务器会返回一个名为Data的首部,用来说明响应生成的日期和时间(服务器通常还会返回一些关于其自身的信息,尽管并非是必需的)。接下来的两个首部大家应该熟悉,就是与POST请求中一样的Content-Type和Content-Length。在本例中,首部Content-Type指定了MIME类型HTML(text/html),其编码类型是ISO-8859-1(这是针对美国英语资源的编码标准)。响应主体所包含的就是所请求资源的HTML源文件(尽管还可能包含纯文本或其他资源类型的二进制数据)。浏览器将把这些数据显示给用户。

注意,这里并没有指明针对该响应的请求类型,不过这对于服务器并不重要。客户端知道每种类型的请求将返回什么类型的数据,并决定如何使用这些数据。

附录:使用Java套接字实现一个可以处理get和post请求的小HTTP服务器程序

/**
 * SimpleHttpServer.java
 */

import java.io.*;
import java.net.*;
import java.util.StringTokenizer;

/**
 * 一个简单的用 Java Socket 编写的 HTTP 服务器应用, 演示了请求和应答的协议通信内容以及
 * 给客户端返回 HTML 文本和二进制数据文件(一个图片), 同时展示了 404, 200 等状态码.
 * 首先运行这个程序,然后打开Web浏览器,键入http://localhost,则这个程序能够显示出浏览器发送了那些信息
 * 并且向浏览器返回一个网页和一副图片, 并测试同浏览器对话.
 * 当浏览器看到 HTML 中带有图片地址时, 则会发出第二次连接来请求图片等资源.
 * 这个例子可以帮您理解 Java 的 HTTP 服务器软件是基于 J2SE 的 Socket 等软件编写的概念, 并熟悉
 * HTTP 协议.
 * 相反的用 Telnet 连接到已有的服务器则可以帮忙理解浏览器的运行过程和服务器端的返回内容.
 *
 * <pre>
 *       当用户在Web浏览器地址栏中输入一个带有http://前缀的URL并按下Enter后,或者在Web页面中某个以http://开头的超链接上单击鼠标,HTTP事务处理的第一个阶段--建立连接阶段就开始了.HTTP的默认端口是80.
 *    随着连接的建立,HTTP就进入了客户向服务器发送请求的阶段.客户向服务器发送的请求是一个有特定格式的ASCII消息,其语法规则为:
 * < Method > < URL > < HTTP Version > < >
 * { <Header>:<Value> < >}*
 * < >
 * { Entity Body }
 *    请求消息的顶端是请求行,用于指定方法,URL和HTTP协议的版本,请求行的最后是回车换行.方法有GET,POST,HEAD,PUT,DELETE等.
 * 在请求行之后是若干个报头(Header)行.每个报头行都是由一个报头和一个取值构成的二元对,报头和取值之间以":"分隔;报头行的最后是回车换行.常见的报头有Accept(指定MIME媒体类型),Accept_Charset(响应消息的编码方式),Accept_Encoding(响应消息的字符集),User_Agent(用户的浏览器信息)等.
 *    在请求消息的报头行之后是一个回车换行,表明请求消息的报头部分结束.在这个之后是请求消息的消息实体(Entity Body).具体的例子参看httpRequest.txt.
 *     Web服务器在收到客户请求并作出处理之后,要向客户发送应答消息.与请求消息一样,应答消息的语法规则为:
 * < HTTP Version> <Status Code> [<Message>]< >
 * { <Header>:<Value> < > } *
 * < >
 * { Entity Body }
 *    应答消息的第一行为状态行,其中包括了HTTP版本号,状态码和对状态码进行简短解释的消息;状态行的最后是回车换行.状态码由3位数字组成,有5类: 
 * 参看:HTTP应答码及其意义 
 * 
 * 1XX 保留 
 * 2XX 表示成功 
 * 3XX 表示URL已经被移走 
 * 4XX 表示客户错误 
 * 5XX 表示服务器错误 
 * 例如:415,表示不支持改媒体类型;503,表示服务器不能访问.最常见的是200,表示成功.常见的报头有:Last_Modified(最后修改时间),Content_Type(消息内容的MIME类型),Content_Length(内容长度)等.
 *    在报头行之后也是一个回车换行,用以表示应答消息的报头部分的结束,以及应答消息实体的开始.
 *    下面是一个应答消息的例子:
 * HTTP/1.0 200 OK
 * Date: Moday,07-Apr-97 21:13:02 GMT
 * Server:NCSA/1.1
 * MIME_Version:1.0
 * Content_Type:text/html
 * Last_Modified:Thu Dec 5 09:28:01 1996
 * Coentent_Length:3107
 * 
 * <HTML><HEAD><TITLE></HTML>
 * 
 * 在用Java语言实现HTTP服务器时,首先启动一个java.net.ServerSocket在提供服务的端口上监听连接.向客户返回文本时,可以用PrintWriter,但是如果返回二进制数据,则必须使用OutputStream.write(byte[])方法,返回的应答消息字符串可以使用String.getBytes()方法转换为字节数组返回,或者使用PrintStream的print()方法写入文本,用write(byte[])方法写入二进制数据.
 * 
 * </pre>
 * @author 刘长炯
 * @version 1.0 2007-07-24 Sunday
 */
public class SimpleHttpServer implements Runnable {
    /**
     * 
     */
    ServerSocket serverSocket;//服务器Socket
    
    /**
     * 服务器监听端口, 默认为 80.
     */
    public static int PORT=80;//标准HTTP端口
    
    /**
     * 开始服务器 Socket 线程.
     */
    public SimpleHttpServer() {
        try {
            serverSocket=new ServerSocket(PORT);
        } catch(Exception e) {
            System.out.println("无法启动HTTP服务器:"+e.getLocalizedMessage());
        }
        if(serverSocket==null)  System.exit(1);//无法开始服务器
        new Thread(this).start();
        System.out.println("HTTP服务器正在运行,端口:"+PORT);
    }
    
    /**
     * 运行服务器主线程, 监听客户端请求并返回响应.
     */
    public void run() {
        while(true) {
            try {
                Socket client=null;//客户Socket
                client=serverSocket.accept();//客户机(这里是 IE 等浏览器)已经连接到当前服务器
                if(client!=null) {
                    System.out.println("连接到服务器的用户:"+client);
                    try {
                        // 第一阶段: 打开输入流
                        BufferedReader in=new BufferedReader(new InputStreamReader(
                                client.getInputStream()));
                        
                        System.out.println("客户端发送的请求信息: ***************");
                        // 读取第一行, 请求地址
                        String line=in.readLine();
                        System.out.println(line);
                        String resource=line.substring(line.indexOf(‘/‘),line.lastIndexOf(‘/‘)-5);
                        //获得请求的资源的地址
                        resource=URLDecoder.decode(resource, "UTF-8");//反编码 URL 地址
                        String method = new StringTokenizer(line).nextElement().toString();// 获取请求方法, GET 或者 POST

                        // 读取所有浏览器发送过来的请求参数头部信息
                        while( (line = in.readLine()) != null) {
                            System.out.println(line);
                            
                            if(line.equals("")) break;
                        }
                        
                        // 显示 POST 表单提交的内容, 这个内容位于请求的主体部分
                        if("POST".equalsIgnoreCase(method)) {
                            System.out.println(in.readLine());
                        }
                        
                        System.out.println("请求信息结束 ***************");
                        System.out.println("用户请求的资源是:"+resource);
                        System.out.println("请求的类型是: " + method);

                        // GIF 图片就读取一个真实的图片数据并返回给客户端
                        if(resource.endsWith(".gif")) {
                            fileService("images/test.gif", client);
                            closeSocket(client);
                            continue;
                        }
                        
                        // 请求 JPG 格式就报错 404
                        if(resource.endsWith(".jpg")) {
                                                    PrintWriter out=new PrintWriter(client.getOutputStream(),true);
                        out.println("HTTP/1.0 404 Not found");//返回应答消息,并结束应答
                        out.println();// 根据 HTTP 协议, 空行将结束头信息
                        out.close();
                        closeSocket(client);
                        continue;
                        } else {
                            // 用 writer 对客户端 socket 输出一段 HTML 代码
                            PrintWriter out=new PrintWriter(client.getOutputStream(),true);
                            out.println("HTTP/1.0 200 OK");//返回应答消息,并结束应答
                            out.println("Content-Type:text/html;charset=GBK");
                            out.println();// 根据 HTTP 协议, 空行将结束头信息

                            out.println("<h1> Hello Http Server</h1>");
                            out.println("你好, 这是一个 Java HTTP 服务器 demo 应用.<br>");
                            out.println("您请求的路径是: " + resource + "<br>");
                            out.println("这是一个支持虚拟路径的图片:<img src=‘abc.gif‘><br>" +
                                    "<a href=‘abc.gif‘>点击打开abc.gif, 是个服务器虚拟路径的图片文件.</a>");
                            out.println("<br>这是个会反馈 404 错误的的图片:<img src=‘test.jpg‘><br><a href=‘test.jpg‘>点击打开test.jpg</a><br>");
                            out.println("<form method=post action=‘/‘>POST 表单 <input name=username value=‘用户‘> <input name=submit type=submit value=submit></form>");
                            out.close();

                            closeSocket(client);
                        }
                    } catch(Exception e) {
                        System.out.println("HTTP服务器错误:"+e.getLocalizedMessage());
                    }
                }
                //System.out.println(client+"连接到HTTP服务器");//如果加入这一句,服务器响应速度会很慢
            } catch(Exception e) {
                System.out.println("HTTP服务器错误:"+e.getLocalizedMessage());
            }
        }
    }
    
    /**
     * 关闭客户端 socket 并打印一条调试信息.
     * @param socket 客户端 socket.
     */
    void closeSocket(Socket socket) {
        try {
            socket.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
                            System.out.println(socket + "离开了HTTP服务器");        
    }
    
    /**
     * 读取一个文件的内容并返回给浏览器端.
     * @param fileName 文件名
     * @param socket 客户端 socket.
     */
        void fileService(String fileName, Socket socket)
    {
            
        try
        {
            PrintStream out = new PrintStream(socket.getOutputStream(), true);
            File fileToSend = new File(fileName);
            if(fileToSend.exists() && !fileToSend.isDirectory())
            {
                out.println("HTTP/1.0 200 OK");//返回应答消息,并结束应答
                out.println("Content-Type:application/binary");
                out.println("Content-Length:" + fileToSend.length());// 返回内容字节数
                out.println();// 根据 HTTP 协议, 空行将结束头信息
                
                FileInputStream fis = new FileInputStream(fileToSend);
                byte data[] = new byte[fis.available()];
                fis.read(data);
                out.write(data);
                out.close();
                fis.close();
            }
        }
        catch(Exception e)
        {
            System.out.println("传送文件时出错:" + e.getLocalizedMessage());
        }
    }
    
    /**
     * 打印用途说明.
     */
    private static void usage() {
        System.out.println("Usage: java HTTPServer <port> Default port is 80.");
    }
    
    
    /**
     * 启动简易 HTTP 服务器
     * @param args 
     */
    public static void main(String[] args) {
        try {
            if(args.length != 1) {
                usage();
            } else if(args.length == 1) {
                PORT = Integer.parseInt(args[0]);
            }
        } catch (Exception ex) {
            System.err.println("Invalid port arguments. It must be a integer that greater than 0");
        }
        
        new SimpleHttpServer();
    }
    
}

时间: 2024-10-10 18:17:29

HTTP头部详解及使用Java套接字处理HTTP请求的相关文章

Java网络编程和NIO详解开篇:Java网络编程基础

Java网络编程和NIO详解开篇:Java网络编程基础 计算机网络编程基础 转自:https://mp.weixin.qq.com/s/XXMz5uAFSsPdg38bth2jAA 我们是幸运的,因为我们拥有网络.网络是一个神奇的东西,它改变了你和我的生活方式,改变了整个世界. 然而,网络的无标度和小世界特性使得它又是复杂的,无所不在,无所不能,以致于我们无法区分甚至无法描述. 对于一个码农而言,了解网络的基础知识可能还是从了解定义开始,认识OSI的七层协议模型,深入Socket内部,进而熟练地

HTTP头部详解

因为之后的HTTP头注入要学习这些所以就看了.觉得很不错,算是学习前的科普. <HTTP头部详解>转载自:http://www.cnblogs.com/lcamry/p/5763040.html 1. Accept:告诉WEB服务器自己接受什么介质类型,*/* 表示任何类型,type/* 表示该类型下的所有子类型,type/sub-type. 2. Accept-Charset: 浏览器申明自己接收的字符集 Accept-Encoding: 浏览器申明自己接收的编码方法,通常指定压缩方法,是否

javaweb part3 ----HTTP协议详解,以及java中request response的类以及方法

HTTP协议详解Requsert Headers 浏览器 向服务器 发送的 请求信息Get /Myweb/persons.html HTTP/1.1 (必须)Host:localhost:8080Connection:keep-alive Accept支持格式:Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8User-Agent:兼容浏览器的版本Accept-

详解User Defined Java Class步骤(一)

 详解User Defined Java Class步骤(一) kettle中的"user defined java class"步骤,也称UDJC步骤,从4.0版本就有,功能非常强大,无所不能:可以在其中写任意代码,却不影响效率.本文将详细介绍在不同场景中用示例展示如果使用该步骤,由于内容非常多,便于阅读方便,把内容分成三部分,请完整看完全部内容,示例代码在这里下载. UDJC步骤工作机制 用户定义java类是从org.pentaho.di.trans.steps.userdef

详解User Defined Java Class步骤(二)

 详解User Defined Java Class步骤(二) kettle中的"user defined java class"步骤,也称UDJC步骤,从4.0版本就有,功能非常强大,无所不能:可以在其中写任意代码,却不影响效率.本文将详细介绍在不同场景中用示例展示如果使用该步骤,由于内容非常多,便于阅读方便,把内容分成三部分,请完整看完全部内容,示例代码在这里下载. 如果没有从第一部分开始,请访问第一部分. 使用步骤参数(Step Parameter) 如果你写了一段代码,如果

详解User Defined Java Class步骤(三)

 详解User Defined Java Class步骤(三) kettle中的"user defined java class"步骤,也称UDJC步骤,从4.0版本就有,功能非常强大,无所不能:可以在其中写任意代码,却不影响效率.本文将详细介绍在不同场景中用示例展示如果使用该步骤,由于内容非常多,便于阅读方便,把内容分成三部分,请完整看完全部内容,示例代码在这里下载. 如果没有看第二部分,请先访问第二部分. 错误处理 udjc步骤支持kettle的错误处理特性,从udjc步骤拖动

CSDN Android客户端开发(二):详解如何基于Java用Jsoup爬虫HTML数据

本文参考链接详细介绍如何使用Jsoup包抓取HTML数据,是一个纯java工程,并将其打包成jar包.希望了解如何用java语言爬虫网页的可以看下. 杂家前文就又介绍用HTTP访问百度主页得到html的string字符串,但html的文本数据如果不经过处理就是个文本字符串没有任何效果的.所谓的浏览器就是负责将文本的html"翻译"成看到的界面.在前文有介绍,这个csdn的客户端app分首页.业界.移动.研发.程序员.云计算五大类.以业界为例,http://news.csdn.net/ 

详解linux配置java环境变量

详解linux配置java环境变量 一. 解压安装jdk 在shell终端下进入jdk-6u14-linux-i586.bin文件所在目录, 执行命令 ./jdk-6u14-linux-i586.bin 这时会出现一段协议,连继敲回车,当询问是否同意的时候,输入yes,回车.之后会在当前目录下生成一个jdk1.6.0_14目录,你可以将它复制到 任何一个目录下. 二. 需要配置的环境变量 1. PATH环境变量.作用是指定命令搜索路径,在shell下面执行命令时,它会到PATH变量所指定的路径中

详解Javac将java文件编译为class文件的过程

Java编译器总的来说分为前端编译器,JIT(just in time compiler)编译器,AOT(Ahead Of Time Compiler)编译器三种. 前端编译器: 将Java文件编译为class文件的编译器,目前主要有以下两个,Sun提供的Javac 和Eclipse JDT中的增量式编译器(ECJ) JIT编译器: 虚拟机后端运行期编译器,把字节码转换为机器码的过程.HotSpot Vm中提供的C1, C2编译器 AOT编译器:直接把Java文件转换为本地机器码的过程. GNU