一、Http网络请求的原理
Http是通过TCP实现的可靠的网络传输。(需要了解的知识TCP/UDP编程)
那么Http是如何过度到TCP实现客户端与服务器的交互的呢?
①、当客户端执行网络请求的时候,会从URL地址中解析出URL的主机名(Host),并将主机地址转换成IP
(主机名是什么:类似http://write.blog.csdn.net/postedit
IP是什么:类似192.168.1.1。主机名是IP的表示,因为主机名表达的含义肯定比数字容易记住
)
②、从URL解析出服务器的所用的端口号。
③、客户端用TCP连接服务器
④、连接成功之后,获取输出流,并将数据以报文(报文的格式之后再讲)的形式传递给服务器(Request)
⑤、当服务器接受到数据之后,进行判断和解析吗,并回送一条响应报文(Response)
⑥、客户端从输入流中获取报文,并解析。
⑦、最后关闭网络的连接。
二、Http请求服务器的方式。(详细知识,需要了解报文的格式,之后才能做实验)
①、GET请求
作用:获取服务器的某个资源,或者说是告诉服务器,我想查询一些信息。
特点:明文传输(直接将传送给服务器的数据,写在URL上)。
常用:跳转到另一个网站,网站名就是GET传输,然后向服务器请求获取该网站。
②、POST请求
作用:向服务器传递数据。一般用来提交HTML表单时使用。服务器处理这些数据
特点:表单上传。(写入的数据,不显示在URL上)
常用:注册用户名、密码。传送给服务器处理。
其实,从作用上看,两者其实没有什么区别,都是Client传输数据给Service。不过一个是写在URL上,一个是写在form表单上。然后Service端根据这些上传的数据进行处理。
③、PUT请求
作用:向服务器写入资源。在服务器创造一个文本,然后将Client端的数据,PUT到该文本中。(如果文本名重复的话,前一个文件的内容就会被覆盖)
④、DELETE请求
作用:从服务器中删除资源。(无法保证是否被删除,HTTP规范允许不通知客户端拒绝该请求)
⑤、HEAD请求
作用:让Service端只返回报文的头部(报文分为三部分:状态、头部、数据)。Client端根据报文的头部,能够判断(数据域的信息,判断该报文是否存在数据等。原理需要讲到报文的时候才能说清楚)
⑥、TRACE请求
作用:当Client端发送请求时候,可能需要经过防火墙、代理、网关等应用程序,这些程序可能会修改原始的HTTP请求,然后将最终的HTTP发送给Service端。当Service端接收之后,会回调将修改后的HTTP请求,回传给Client端。
⑦、OPTION请求
作用:询问服务器支持哪些方法(比如POST、GET、OPTION方法)
三、如何解析HTTP报文
①、请求报文的格式
<起始行>
<首部>
<空行>
<数据域>
示例:
GET http://my.csdn.net/my/mycsdn HTTP1.1
Content-Type:text/plain
Host:www.my.csdn.net
Content-Length:30
username="chen"&pwd = 123
②、回复报文的格式
<状态栏>
<首部>
<空行>
<数据>
从格式,来看其实就是状态栏不太一样。
Respone的状态栏:版本号 状态码 对状态码的描述
版本号就没什么好说的。
状态码是什么:
100~199:表示请求以及接收到了
200~299:请求成功。Service已经处理
300~399:要求完成请求需要更进一步的动作
400~499:客户端错误。表示请求语法错误,或者是请求无法执行
500~599:服务器端错误。服务器无法实现请求。
常用状态码:(状态码,后面的就是描述)
200 OK:客户端请求成功
400 Bad Request:客户端请求语法有错。
403 Forbidden:服务器接受到请求,但是拒绝服务
404 Not Found:请求资源不存在。可能原因:URL错误
500 Internal Server Error:服务器发生不可预期错误
503 Server Unavailable:服务器当前无法处理客户端请求
示例:
HTTP1.1 200 OK :表示成功接收
HTTP1.1 200 OK :表示成功接收
③、首部的属性
首部的分类:①、请求的首部属性 ②、响应的首部属性 ③、通用的首部属性 ④、自定义的首部属性
介绍请求的首部属性:
Content-Type:请求数据的类型(MIME)
Content-Length:请求数据的长度
Cache-Control:是否缓存
Host:请求数据的主机名
User-Agent:发出请求的浏览器
Accept:客户端可识别的类型列表
Accept-Encoding:客户端可识别的类型编码
Connection:设置连接方式。KeepAlive表示保持连接
Transfer-Encoding:告诉接收端,为保证可靠传输,对报文采取了什么编码
重要的属性
Content-Type拥有的属性:
超文本标记语言文本 .html text/html
xml文档 .xml text/xml
XHTML文档 .xhtml application/xhtml+xml
普通文本 .txt text/plain
RTF文本 .rtf application/rtf
PDF文档 .pdf application/pdf
Microsoft Word文件 .word application/msword
PNG图像 .png image/png
GIF图形 .gif image/gif
JPEG图形 .jpeg,.jpg image/jpeg
au声音文件 .au audio/basic
MIDI音乐文件 mid,.midi audio/midi,audio/x-midi
RealAudio音乐文件 .ra, .ram audio/x-pn-realaudio
MPEG文件 .mpg,.mpeg video/mpeg
AVI文件 .avi video/x-msvideo
GZIP文件 .gz application/x-gzip
TAR文件 .tar application/x-tar
xml文档 .xml text/xml
XHTML文档 .xhtml application/xhtml+xml
普通文本 .txt text/plain
RTF文本 .rtf application/rtf
PDF文档 .pdf application/pdf
Microsoft Word文件 .word application/msword
PNG图像 .png image/png
GIF图形 .gif image/gif
JPEG图形 .jpeg,.jpg image/jpeg
au声音文件 .au audio/basic
MIDI音乐文件 mid,.midi audio/midi,audio/x-midi
RealAudio音乐文件 .ra, .ram audio/x-pn-realaudio
MPEG文件 .mpg,.mpeg video/mpeg
AVI文件 .avi video/x-msvideo
GZIP文件 .gz application/x-gzip
TAR文件 .tar application/x-tar
Json数据:.json application/json
任意的二进制数据 application/octet-stream
任意的二进制数据 application/octet-stream
四、解析每个请求的HTTP报文
①、GET请求
例:输入http://write.blog.csdn.net/login.php?username=chen&pwd=123
直接将数据写在URL就是明文传输
则创建的报文是:
GET login.php/username=chen&pwd=123 HTTP1.1
Host:http://write.blog.csdn.net
服务器返回的报文数据:
HTTP/1.1 200 OK
Content-Type:text/html
Content-Length:1024
<Html>
网页数据
</Html>
②、POST请求
例:从http://write.blog.csdn.net/login.php中,上传HTML表单
POST login.php HTTP1.1
Accept-Encoding:gzip
Content-Type:multipart/form-data; boundary=ABCD //Content-Type:表示这是一个表单 boundary:表示分隔符
Content-Length:22350
Host:http://write.blog.csdn.net
Connection:Keep-Alive
--ABCD //通过--加上boundary的值:表示这是表单的一个数据
Content-Disposition:form-data;name="username" //key值
Content-Type:text/plain; charset=UTF-8 //key的类型
Content-Transfer-Encoding:8bit //key的大小
//空白行,必须加
chen //value的值
--ABCD
Content-Disposition:form-data;name="images";
filename="storage/emulated/0/Camera/picture/tempeter.jpg"
Content-Type: application/octet-stream
Content-Transfer-Encoding:binary
(这里是图片的二进制数据)
--ABCD-- //表示终止
服务器返回的数据:
HTTP/1.1 200 OK
HTTP/1.1 200 OK
Content-Type:text/plain
Content-Length:21
success
③、PUT请求
PUT new-text.txt HTTP/1.1
Host:www.myhost.cn
Content-Type:text/html
Content-Length:21
This is file content //文件的内容
服务器返回的数据:
HTTP/1.1 200 OK
Content-Type:text/html
Content-Length:1024
Location:www.myhost.cn/new-text.txt
www.myhost.cn/new-text.txt
④、HEAD请求
HEAD login.php HTTP/1.1
Content-Type:text/plain
Content-Length:1024
服务器返回的数据
HTTP/1.1 200 OK
Content-Type:text/html
Content-Length:12324
⑤、TRANCE请求 假设经过了一层代理商(proxy.vpn.com)
TRANCE login.php HTTP/1.1
Host:www.myhost.com
服务器返回的数据:
HTTP/1.1 200 OK
HTTP/1.1 200 OK
Content-Type:text/plain
Content-Length:21
TRANCE login.php HTTP/1.1
Host:www.myhost.com
Via:www.proxy.vpn.com
⑥、OPTION请求
OPTION login.php HTTP/1.1
Host:www.myhost.com
服务器返回的数据:
HTTP/1.1 200 OK
Allow:GET、POST、PUT、OPTIONS
Content-Length:0
五、利用TCP模拟HTTP传输
①、创建SimpleServer等待Socket连接。
public class SimpleServer extends Thread{
public static final int PORT = 8000;
private ServerSocket mServerSocket = null;
public SimpleServer(){
try {
mServerSocket = new ServerSocket(PORT);
} catch (IOException e) {
// TODO Auto-generated catch block
System.out.println("端口"+PORT+"已经被占用,请换一个端口");
}
}
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
//死循环,接收客户端的TCP
while(true && mServerSocket != null){
System.out.println("等待客户端连接");
//调用线程处理数据
}
}
}
②、TCP连接成功后,Server端处理数据(创建DeliverThread来处理数据)
public class DeliverThread extends Thread {
private Socket mServerSocket;
private BufferedReader mBufferedReader;
private PrintWriter mPrintWriter;
public DeliverThread(Socket socket){
mServerSocket = socket;
}
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
try {
//获取输入输出流
mBufferedReader = new BufferedReader(new InputStreamReader(mServerSocket.getInputStream()));
mPrintWriter = new PrintWriter(mServerSocket.getOutputStream());
//处理Client发送过来的数据
parseRequest();
//回复Client的数据
handleResponse();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
try {
mBufferedReader.close();
mPrintWriter.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
private void parseRequest() throws IOException{
String line = null;
int lineNum = 0;
//首先解析头部状态数据
//然后解析头部数据
//最后解析传递的数据
while((line = mBufferedReader.readLine()) != null){
//如果为第一行,那么一定是状态栏
if (lineNum == 0){
parseHeaderLine(line);
}
lineNum++;
}
System.out.println("解析Service数据完成");
}
private void parseHeaderLine(String line){
//分割Params
String data[] = line.split(" ");
System.out.println("请求类型"+data[0]);
System.out.println("请求的网址"+data[1]);
System.out.println("Http版本:"+data[2]);
}
private void handleResponse(){
mPrintWriter.println("Http/1.1 200 OK");
mPrintWriter.println("Content-Type:text/plain");
mPrintWriter.println("Content-Length:300");
}
}
③、创建Socket端。
ublic class HttpPost extends Thread {
private String mUrl;
private Map<String, String> mParams;
private Socket mClientSocket;
public HttpPost(String url){
mUrl = url;
mParams = new HashMap<>();
}
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
try {
mClientSocket = new Socket(mUrl,SimpleServer.PORT);
BufferedReader reader = new BufferedReader(new InputStreamReader(mClientSocket.getInputStream()));
PrintWriter writer = new PrintWriter(mClientSocket.getOutputStream());
writeHeader(writer);
waitResponse(reader);
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
try {
mClientSocket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private void writeHeader(PrintWriter writer){
writer.println("POST login.php HTTP/1.1");
}
private void waitResponse(BufferedReader reader){
StringBuilder builder = new StringBuilder();
String str = null;
try {
while((str = reader.readLine()) != null){
builder.append(str);
}
System.out.println(builder.toString());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void addParams(String key,String value){
mParams.put(key,value);
}
}