CSDN编者按】本文将通过简单的术语和真实世界的例子解释 JavaScript 中 this 及其用途,并告诉你写出好的代码为何如此重要。
我看到许多文章在介绍 JavaScript 的 this 时都会假设你学过某种面向对象的编程语言,比如 Java、C++ 或 Python 等。但这篇文章面向的读者是那些不知道 this 是什么的人。我尽量不用任何术语来解释 this 是什么,以及 this 的用法。
也许你一直不敢解开 this 的秘密,因为它看起来挺奇怪也挺吓人的。或许你只在 StackOverflow 说你需要用它的时候(比如在 React 里实现某个功能)才会使用。
在深入介绍 this 之前,我们首先需要理解函数式编程和面向对象编程之间的区别。
你可能不知道,JavaScript 同时拥有面向对象和函数式的结构,所以你可以自己选择用哪种风格,或者两者都用。
我在很早以前使用 JavaScript 时就喜欢函数式编程,而且会像躲避瘟疫一样避开面向对象编程,因为我不理解面向对象中的关键字,比如 this。我不知道为什么要用 this。似乎没有它我也可以做好所有的工作。
而且我是对的。
在某种意义上 。也许你可以只专注于一种结构并且完全忽略另一种,但这样你只能是一个 JavaScript 开发者。为了解释函数式和面向对象之间的区别,下面我们通过一个数组来举例说明,数组的内容是 Facebook 的好友列表。
假设你要做一个 Web 应用,当用户使用 Facebook 登录你的 Web 应用时,需要显示他们的 Facebook 的好友信息。你需要访问 Facebook 并获得用户的好友数据。这些数据可能是 firstName、lastName、username、numFriends、friendData、birthday 和 lastTenPosts 等信息。
const data=[ { firstName: 'Bob', lastName: 'Ross', username: 'bob.ross', numFriends: 125, birthday: '2/23/1985', lastTenPosts: ['What a nice day', 'I love Kanye West', ...], }, ... ]
假设上述数据是你通过 Facebook API 获得的。现在需要将其转换成方便你的项目使用的格式。我们假设你想显示的好友信息如下:
函数式的方式就是将整个数组或者数组中的某个元素传递给某个函数,然后返回你需要的信息:
const fullNames=getFullNames(data) // ['Ross, Bob', 'Smith, Joanna', ...]
首先我们有 Facebook API 返回的原始数据。为了将其转换成需要的格式,首先要将数据传递给一个函数,函数的输出是(或者包含)经过修改的数据,这些数据可以在应用中向用户展示。
我们可以用类似的方法获得随机三篇文章,并且计算距离好友生日的天数。
函数式的方式是:将原始数据传递给一个函数或者多个函数,获得对你的项目有用的数据格式。
对于编程初学者和 JavaScript 初学者,面向对象的概念可能有点难以理解。其思想是,我们要将每个好友变成一个对象,这个对象能够生成你一切开发者需要的东西。
你可以创建一个对象,这个对象对应于某个好友,它有 fullName 属性,还有两个函数 getThreeRandomPosts 和 getDaysUntilBirthday。
function initializeFriend(data) { return { fullName: `${data.firstName} ${data.lastName}`, getThreeRandomPosts: function() { // get three random posts from data.lastTenPosts }, getDaysUntilBirthday: function() { // use data.birthday to get the num days until birthday } }; } const objectFriends=data.map(initializeFriend) objectFriends[0].getThreeRandomPosts() // Gets three of Bob Ross's posts
面向对象的方式就是为数据创建对象,每个对象都有自己的状态,并且包含必要的信息,能够生成需要的数据。
你也许从来没想过要写上面的 initializeFriend 代码,而且你也许认为,这种代码可能会很有用。但你也注意到,这并不是真正的面向对象。
其原因就是,上面例子中的 getThreeRandomPosts 或 getdaysUntilBirtyday 能够正常工作的原因其实是闭包。因为使用了闭包,它们在 initializeFriend 返回之后依然能访问 data。关于闭包的更多信息可以看看这篇文章:作用域和闭包(https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch5.md)。
还有一个方法该怎么处理?我们假设这个方法叫做 greeting。注意方法(与 JavaScript 的对象有关的方法)其实只是一个属性,只不过属性值是函数而已。我们想在 greeting 中实现以下功能:
function initializeFriend(data) { return { fullName: `${data.firstName} ${data.lastName}`, getThreeRandomPosts: function() { // get three random posts from data.lastTenPosts }, getDaysUntilBirthday: function() { // use data.birthday to get the num days until birthday }, greeting: function() { return `Hello, this is ${fullName}'s data!` } }; }
这样能正常工作吗?
不能!
我们新建的对象能够访问 initializeFriend 中的一切变量,但不能访问这个对象本身的属性或方法。当然你会问,
难道不能在 greeting 中直接用 data.firstName 和 data.lastName 吗?
当然可以。但要是想在 greeting 中加入距离好友生日的天数怎么办?我们最好还是有办法在 greeting 中调用 getDaysUntilBirthday。
这时轮到 this 出场了!
this 在不同的环境中可以指代不同的东西。默认的全局环境中 this 指代的是全局对象(在浏览器中 this 是 window 对象),这没什么太大的用途。而在 this 的规则中具有实用性的是这一条:
如果在对象的方法中使用 this,而该方法在该对象的上下文中调用,那么 this 指代该对象本身。
你会说“在该对象的上下文中调用”……是啥意思?
别着急,我们一会儿就说。
所以,如果我们想从 greeting 中调用 getDaysUntilBirtyday 我们只需要写 this.getDaysUntilBirthday,因为此时的 this 就是对象本身。
附注:不要在全局作用域的普通函数或另一个函数的作用域中使用 this!this 是个面向对象的东西,它只在对象的上下文(或类的上下文)中有意义。
我们利用 this 来重写 initializeFriend:
function initializeFriend(data) { return { lastTenPosts: data.lastTenPosts, birthday: data.birthday, fullName: `${data.firstName} ${data.lastName}`, getThreeRandomPosts: function() { // get three random posts from this.lastTenPosts }, getDaysUntilBirthday: function() { // use this.birthday to get the num days until birthday }, greeting: function() { const numDays=this.getDaysUntilBirthday() return `Hello, this is ${this.fullName}'s data! It is ${numDays} until ${this.fullName}'s birthday!` } }; }
现在,在 initializeFriend 执行结束后,该对象需要的一切都位于对象本身的作用域之内了。我们的方法不需要再依赖于闭包,它们只会用到对象本身包含的信息。
好吧,这是 this 的用法之一,但你说过 this 在不同的上下文中有不同的含义。那是什么意思?为什么不一定会指向对象自己?
有时候,你需要将 this 指向某个特定的东西。一种情况就是事件处理函数。比如我们希望在用户点击好友时打开好友的 Facebook 首页。我们会给对象添加下面的 onClick 方法:
function initializeFriend(data) { return { lastTenPosts: data.lastTenPosts, birthday: data.birthday, username: data.username, fullName: `${data.firstName} ${data.lastName}`, getThreeRandomPosts: function() { // get three random posts from this.lastTenPosts }, getDaysUntilBirthday: function() { // use this.birthday to get the num days until birthday }, greeting: function() { const numDays=this.getDaysUntilBirthday() return `Hello, this is ${this.fullName}'s data! It is ${numDays} until ${this.fullName}'s birthday!` }, onFriendClick: function() { window.open(`https://facebook.com/${this.username}`) } }; }
注意我们在对象中添加了 username 属性,这样 onFriendClick 就能访问它,从而在新窗口中打开该好友的 Facebook 首页。现在只需要编写 HTML:
<button id="Bob_Ross"> <!-- A bunch of info associated with Bob Ross --> </button>
还有 JavaScript:
const bobRossObj=initializeFriend(data[0]) const bobRossDOMEl=document.getElementById('Bob_Ross') bobRossDOMEl.addEventListener("onclick", bobRossObj.onFriendClick)
在上述代码中,我们给 Bob Ross 创建了一个对象。然后我们拿到了 Bob Ross 对应的 DOM 元素。然后执行 onFriendClick 方法来打开 Bob 的 Facebook 主页。似乎没问题,对吧?
有问题!
哪里出错了?
注意我们调用 onclick 处理程序的代码是 bobRossObj.onFriendClick。看到问题了吗?要是写成这样的话能看出来吗?
bobRossDOMEl.addEventListener("onclick", function() { window.open(`https://facebook.com/${this.username}`) })
现在看到问题了吗?如果把事件处理程序写成 bobRossObj.onFriendClick,实际上是把 bobRossObj.onFriendClick 上保存的函数拿出来,然后作为参数传递。它不再“依附”在 bobRossObj 上,也就是说,this 不再指向 bobRossObj。它实际指向全局对象,也就是说 this.username 不存在。似乎我们没什么办法了。
轮到绑定上场了!
我们需要明确地将 this 绑定到 bobRossObj 上。我们可以通过 bind 实现:
const bobRossObj=initializeFriend(data[0]) const bobRossDOMEl=document.getElementById('Bob_Ross') bobRossObj.onFriendClick=bobRossObj.onFriendClick.bind(bobRossObj) bobRossDOMEl.addEventListener("onclick", bobRossObj.onFriendClick)
之前,this 是按照默认的规则设置的。但使用 bind 之后,我们明确地将 bobRossObj.onFriendClick 中的 this 的值设置为 bobRossObj 对象本身。
到此为止,我们看到了为什么要使用 this,以及为什么要明确地绑定 this。最后我们来介绍一下,this 实际上是箭头函数。
你也许注意到了箭头函数最近很流行。人们喜欢箭头函数,因为很简洁、很优雅。而且你还知道箭头函数和普通函数有点区别,尽管不太清楚具体区别是什么。
简而言之,两者的区别在于:
在定义箭头函数时,不管 this 指向谁,箭头函数内部的 this 永远指向同一个东西。
嗯……这貌似没什么用……似乎跟普通函数的行为一样啊?
我们通过 initializeFriend 举例说明。假设我们想添加一个名为 greeting 的函数:
function initializeFriend(data) { return { lastTenPosts: data.lastTenPosts, birthday: data.birthday, username: data.username, fullName: `${data.firstName} ${data.lastName}`, getThreeRandomPosts: function() { // get three random posts from this.lastTenPosts }, getDaysUntilBirthday: function() { // use this.birthday to get the num days until birthday }, greeting: function() { function getLastPost() { return this.lastTenPosts[0] } const lastPost=getLastPost() return `Hello, this is ${this.fullName}'s data! ${this.fullName}'s last post was ${lastPost}.` }, onFriendClick: function() { window.open(`https://facebook.com/${this.username}`) } }; }
这样能运行吗?如果不能,怎样修改才能运行?
答案是不能。因为 getLastPost 没有在对象的上下文中调用,因此getLastPost 中的 this 按照默认规则指向了全局对象。
你说没有“在对象的上下文中调用”……难道它不是从 initializeFriend 返回的内部调用的吗?如果这还不叫“在对象的上下文中调用”,那我就不知道什么才算了。
我知道“在对象的上下文中调用”这个术语很模糊。也许,判断函数是否“在对象的上下文中调用”的好方法就是检查一遍函数的调用过程,看看是否有个对象“依附”到了函数上。
我们来检查下执行 bobRossObj.onFriendClick() 时的情况。“给我对象 bobRossObj,找到其中的 onFriendClick 然后调用该属性对应的函数”。
我们同样检查下执行 getLastPost() 时的情况。“给我名为 getLastPost 的函数然后执行。”看到了吗?我们根本没有提到对象。
好了,这里有个难题来测试你的理解程度。假设有个函数名为 functionCaller,它的功能就是调用一个函数:
functionCaller(fn) { fn() }
如果调用 functionCaller(bobRossObj.onFriendClick) 会怎样?你会认为 onFriendClick 是“在对象的上下文中调用”的吗?this.username有定义吗?
我们来检查一遍:“给我 bobRosObj 对象然后查找其属性 onFriendClick。取出其中的值(这个值碰巧是个函数),然后将它传递给 functionCaller,取名为 fn。然后,执行名为 fn 的函数。”注意该函数在调用之前已经从 bobRossObj 对象上“脱离”了,因此并不是“在对象的上下文中调用”的,所以 this.username 没有定义。
这时可以用箭头函数解决这个问题:
function initializeFriend(data) { return { lastTenPosts: data.lastTenPosts, birthday: data.birthday, username: data.username, fullName: `${data.firstName} ${data.lastName}`, getThreeRandomPosts: function() { // get three random posts from this.lastTenPosts }, getDaysUntilBirthday: function() { // use this.birthday to get the num days until birthday }, greeting: function() { const getLastPost=()=> { return this.lastTenPosts[0] } const lastPost=getLastPost() return `Hello, this is ${this.fullName}'s data! ${this.fullName}'s last post was ${lastPost}.` }, onFriendClick: function() { window.open(`https://facebook.com/${this.username}`) } }; }
上述代码的规则是:
在定义箭头函数时,不管 this 指向谁,箭头函数内部的 this 永远指向同一个东西。
箭头函数是在 greeting 中定义的。我们知道,在 greeting 内部的 this 指向对象本身。因此,箭头函数内部的 this 也指向对象本身,这正是我们需要的结果。
this 有时很不好理解,但它对于开发 JavaScript 应用非常有用。本文当然没能介绍 this 的所有方面。一些没有涉及到的话题包括:
我建议你首先问问自己在这些情况下的 this,然后在浏览器中执行代码来检验你的结果。
想学习更多关 于this 的内容,可参考《你不知道的 JS:this 和对象原型》:
如果你想测试自己的知识,可参考《你不知道的JS练习:this和对象原型》:
原文:https://medium.freecodecamp.org/a-deep-dive-into-this-in-javascript-why-its-critical-to-writing-good-code-7dca7eb489e7
作者:Austin Tackaberry,Human API 的软件工程师
译者:弯月,责编:屠敏
“征稿啦”
CSDN 公众号秉持着「与千万技术人共成长」理念,不仅以「极客头条」、「畅言」栏目在第一时间以技术人的独特视角描述技术人关心的行业焦点事件,更有「技术头条」专栏,深度解读行业内的热门技术与场景应用,让所有的开发者紧跟技术潮流,保持警醒的技术嗅觉,对行业趋势、技术有更为全面的认知。
如果你有优质的文章,或是行业热点事件、技术趋势的真知灼见,或是深度的应用实践、场景方案等的新见解,欢迎联系 CSDN 投稿,联系方式:微信(guorui_1118,请备注投稿+姓名+公司职位),邮箱(guorui@csdn.net)。
理解Tomcat其实首先就是要理解Web的运行原理,基本上每个人都上网,但是既然我们自己在学习在做动态网页,有没有真正考虑过我们在浏览网页时底层的一些基本运行原理。
这中间其实是你的客户端浏览器与服务器端的通信过程,具体如下:
就是这样的一个简单过程,这个过程很多书上也有,还有图。但是,就是这个样子的一个过程,中间就有很多值得探讨的地方。
我们来解析一下,从上面这个过程中分析出
浏览器应该有的功能:
Web服务器应该具有的功能:
HTTP客户程序(浏览器)和HTTP服务器分别由不同的软件开发商提供,目前
最流行的浏览器IE,Firefox,Google Chrome,Apple Safari等等,最常用的Web服务器有IIS,Tomcat,Weblogic,jboss等。不同的浏览器和Web服务器都是不同的编程语言编写的,那么用C++编写的HTTP客户端浏览器能否与用JAVA编写的Web服务进行通信呢?允许在苹果系统上的Safari浏览器能否与运行在Windows或者Linux平台上的Web服务器进行通信呢?
前面说了这么多,就是引出这一句话。
为什么不同语言编写,不同平台运行的软件双方能够看懂对方的数据呢?这主要归功于HTTP协议。
HTTP协议严格规定了HTTP请求和HTTP响应的数据格式,只要Web服务器与客户端浏览器之间的交换数据都遵守HTTP协议,双方就能看懂对方发送的数据从而进行交流。
HTTP请求格式
HTTP协议规定,HTTP请求由三部分组成
看一个HTTP请求的例子:
//请求方法,URI和HTTP协议的版本
POST /servlet/default.JSP HTTP/1.1
//========请求头==================//
Accept: text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Accept-Charset: iso-8859-1, utf-8, utf-16, *;q=0.1
Accept-Encoding: deflate, gzip, x-gzip, identity, *;q=0
Connection: Keep-Alive
Host: localhost
Referer: HTTP://localhost/ch8/SendDetails.htm
User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)
Content-Length: 33
Content-Type: application/x-www-form-urlencoded
//========请求头==================//
//请求正文
LastName=Franks&FirstName=Michael
1.请求方法,URI和HTTP协议版本
这三个都在HTTP请求的第一行,以空格分开,以上代码中”post”为请求方式,”/servlet/default.JSP”为URI, ”HTTP/1.1”为HTTP协议版本
2.请求头
请求头包含许多有关客户端环境和请求正文的有用信息。比如包含浏览器类型,所用语言,请求正文类型以及请求正文长度等。
3.请求正文
HTTP协议规定,请求头和请求正文之间必须以空行分割(\r\n),这个空行很重要,它表示请求头已经结束,接下来是请求正文。在请求正文中可以包含客户以Post方式提交的数据表单
LastName=Franks&FirstName=Michael
HTTP响应格式
和请求相似,HTTP响应也是由3部分组成
HTTP/1.1 200 OK
Date: Tues, 07 May 2013 14:16:18 GMT
Server: Apache/1.3.31 (Unix) mod_throttle/3.1.2
Last-Modified: Tues, 07 May 2013 14:16:18
ETag: "dd7b6e-d29-39cb69b2"
Accept-Ranges: bytes
Content-Length: 3369
Connection: close
Content-Type: text/html
<html>
<head>
<title>hello</title>
</head>
<body>
<h1>hello</h1>
</body>
</html>
1.HTTP协议版本,状态代码和描述
HTTP响应第一行也是3个内容,同样以空格分隔,依次是HTTP协议版本,状态代码以及对状态代码的描述。状态代码200表示服务器已经成功处理了客户端发送的请求。状态代码是三位整数,以1,2,3,4,5开头,具体有哪些常见的状态代码这里不再多做描述。
2.响应头
响应头主要是一些描述信息,如服务器类型,正文类型和正文长度等
3.响应正文
响应正文就是服务器返回的具体数据,它是浏览器真正请求访问的信息,最常见的当然就是HTML。同样,响应正文和响应头同样需要以空行分割
分析
前面说了这么多,描述的HTTP请求和响应的内容,主要是引出下面的内容,既然Tomcat可以作为Web服务器,那么我们自己能不能根据HTTP请求和响应搭建一个自己简单的Web服务器呢?
我们在启动好Tomcat后,访问的地址如HTTP://127.0.0.1:8080/index.html, 经过分析,前面的127.0.0.1无非就是主机IP,而8080就是Tomcat监听端口, index.html是我们需要访问的网址,其实也就是Tomcat帮我们读取之后,响应给我们的内容,这是在Tomcat上存在的一个网页。
根据上面的分析,我们自己要建一个简单的Web服务器,那就简单了,就是自己写一段JAVA代码,代替Tomcat监听在8080端口,然后打开网页输入8080端口后进入自己的代码程序,解析HTTP请求,然后在服务器本地读取html文档,最后再响应回去不就行了么?
用JAVA套接字创建HTTP服务器程序
首先做好准备工作,注意整个测试工程的路径是下面这样子的,如图:
这个html文件在工程中我放在了test文件夹下面,接下来上代码
html中的代码很简单
index.html
<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Test</title></head><body>Hello!!</body></html>
HTTPServer.java
package com.ying.http;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.ServerSocket;import java.net.Socket;public class HTTPServer { public static void main(String[] args) { int port;
ServerSocket serverSocket; try {
serverSocket=new ServerSocket(8080);
System.out.println("服务器正在监听:" + serverSocket.getLocalPort()); while(true){ try {
Socket socket=serverSocket.accept();
System.out.println("服务器与一个客户端建立了新的连接,该客户端的地址为:"
+socket.getInetAddress() + ":" + socket.getPort());
service(socket);
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
} public static void service(Socket socket) throws Exception{
InputStream socketIn=socket.getInputStream();
Thread.sleep(500); int size=socketIn.available(); byte[] buffer=new byte[size];
socketIn.read(buffer);
String request=new String(buffer); if(request.equals("")) return;
System.out.println(request); int l=request.indexOf("\r\n");
String firstLineRequest=request.substring(0, l);
String [] parts=firstLineRequest.split(" ");
String uri=parts[1]; //HTTP响应正文类型
String contentType; if(uri.indexOf("html") !=-1 || uri.indexOf("html") !=-1){
contentType="text/html";
}else if(uri.indexOf("jpg") !=-1 || uri.indexOf("jpeg") !=-1){
contentType="image/jpeg";
}else if(uri.indexOf("gif") !=-1){
contentType="image/gif";
}else
contentType="application/octet-stream"; /*创建HTTP响应结果*/
String responseFirstLine="HTTP/1.1 200 OK\r\n";
String responseHeader="Content-Tyep:"+contentType+"\r\n\r\n";
InputStream in=HTTPServer.class.getResourceAsStream("test/" + uri);
OutputStream socketOut=socket.getOutputStream();
socketOut.write(responseFirstLine.getBytes());
socketOut.write(responseHeader.getBytes()); int len=0;
buffer=new byte[128]; while((len=in.read(buffer)) !=-1){
socketOut.write(buffer,0,len);
}
Thread.sleep(1000);
socket.close();
}
}
大家可以看到上面的代码其实就是操作了一些HTTP请求与响应的协议字符串而已。写好上面的代码后,我们启动浏览器,输入HTTP://127.0.0.1:8080/index.html 大家会看到下面的效果:
浏览器自动帮我们输出了index.html下面的文字,
服务器与一个客户端建立了新的连接,该客户端的地址为:/127.0.0.1:57891
GET /index.html HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4
这其实就是一个简单自制的HTTP远程访问,但是上面的代码就只是能根据原始的html返回内容,不能和客户端发生交互,那么现在做一个简单交互。
和服务器进行交互
比如我们输入如下网址:
HTTP://127.0.0.1:8080/servlet/HelloServlet?userName=yingside
那么久应该能出现下面这样的效果
输入:
HTTP://127.0.0.1:8080/servlet/HelloServlet?userName=lovo
就会是这样的效果:
其实这里我们只要对之前的代码做一下简单的修改,让代码能够分析出后面的值就行了。
首先,先来看一下,我们修改之后工程的路径,因为代码中一些路径都是写死了的,为了避免出错,大家先按照我工程的路径搭建就行了.
这里把分析后面参数值的内容专门放在了一个类中,为了让这个类具有通用性,定义了一个接口
Servlet.java
package com.ying.http;import java.io.OutputStream;public interface Servlet { void init() throws Exception; void service(byte[] requestBuffer,OutputStream out) throws Exception;
}
init()方法:为初始化方法,当HTTPServer创建了实现该接口的类的一个实例后,就会立即调用该实例的init()方法
service()方法:用于响应HTTP请求,产生具体的HTTP响应结果。
HelloServlet.java
package com.ying.http;
import java.io.OutputStream;
public class HelloServlet implements Servlet {
public void init() throws Exception {
System.out.println("Hello Servlet is inited");
}
@Override
public void service(byte[] requestBuffer, OutputStream out)
throws Exception {
String request=new String(requestBuffer);
//获得请求的第一行
String firstLineRequest=request.substring(0, request.indexOf("\r\n"));
String [] parts=firstLineRequest.split(" ");
String method=parts[0];//获得HTTP请求中的请求方式
String uri=parts[1];//获得uri
String userName=null;
//如果请求方式为"GET",则请求参数紧跟在HTTP请求的第一行uri的后面
if(method.equalsIgnoreCase("get")&&uri.indexOf("userName") !=-1){
/*假定uri="servlet/HelloServlet?userName=chenjie&password=accp"
*那么参数="userName=chenjie&password=accp",所以这里截取参数字符串
*/
String parameters=uri.substring(uri.indexOf("?"), uri.length());
//通过"&"符号截取字符串
//parts={"userName=chenjie","password=accp"}
parts=parameters.split("&");
//如果想截取出userName的值,再通过"="截取字符串
parts=parts[0].split("=");
userName=parts[1];
}
//如果请求方式为"post",则请求参数在HTTP请求的正文中
//由于请求头和正文有两行空行,所以截取出两行空行,就能截取出正文
if(method.equalsIgnoreCase("post")){
int location=request.indexOf("\r\n\r\n");//提取出两行空行的位置
String content=request.substring(location+4, request.length());
//"post"提交正文里面只有参数,所以只需要
//和"get"方式一样,分割字符串,提取出userName的值
if(content.indexOf("userName") !=-1){
parts=content.split("&");
parts=parts[0].split("=");
userName=parts[1];
}
}
/*创建并发送HTTP响应*/
//发送HTTP响应第一行
out.write("HTTP/1.1 200 OK\r\n".getBytes());
//发送响应头
out.write("Content-Type:text/html\r\n\r\n".getBytes());
//发送HTTP响应正文
out.write("<html><head><title>HelloWord</title></head>".getBytes());
out.write(new String("<body><h1>hello:"+userName+"</h1></body></html>").getBytes());
}
}
说的简单点,其实就是把解析HTTP请求和响应协议字符串放在了这个HelloServlet.JAVA的类里面。最后把HTTPServer做一下修改,干脆重新新建一个类HTTPServerParam.java,大家可以下去自行比较一下两个的区别
HTTPServerParam.java
package com.ying.http;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.ServerSocket;import java.net.Socket;import java.util.HashMap;import java.util.Map;public class HTTPServerParam { private static Map servletCache=new HashMap();// 存放servlet实例的map缓存
public static void main(String[] args) { int port;
ServerSocket serverSocket; try {
serverSocket=new ServerSocket(8080);
System.out.println("服务器正在监听:" + serverSocket.getLocalPort()); while (true) { try {
Socket socket=serverSocket.accept();
System.out.println("服务器与一个客户端建立了新的连接,该客户端的地址为:" + socket.getInetAddress() + ":" + socket.getPort());
service(socket);
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
} public static void service(Socket socket) throws Exception {
InputStream socketIn=socket.getInputStream();
Thread.sleep(500); int size=socketIn.available(); byte[] requestBuffer=new byte[size];
socketIn.read(requestBuffer);
String request=new String(requestBuffer); if (request.equals("")) return;
System.out.println(request); /* 解析HTTP请求 */
// 获得HTTP请求的第一行
int l=request.indexOf("\r\n");
String firstLineRequest=request.substring(0, l); // 解析HTTP请求的第一行,通过空格截取字符串数组
String[] parts=firstLineRequest.split(" ");
String uri=parts[1]; /* 判断如果访问的是Servlet,则动态的调用Servlet对象的service()方法 */
if (uri.indexOf("servlet") !=-1) {
String servletName=null; if (uri.indexOf("?") !=-1)
servletName=uri.substring(uri.indexOf("servlet/") + 8, uri.indexOf("?")); else
servletName=uri.substring(uri.indexOf("servlet/") + 8, uri.length()); // 首先从map里面获取有没有该Servlet
Servlet servlet=(Servlet) servletCache.get(servletName); // 如果Servlet缓存中不存在Servlet对象,就创建它,并把它存到map缓存中
if (servlet==null) {
servlet=(Servlet) Class.forName("com.ying.http." + servletName).newInstance();
servlet.init();
servletCache.put(servletName, servlet);
} // 调用Servlet的service()方法
servlet.service(requestBuffer, socket.getOutputStream());
Thread.sleep(1000);
socket.close(); return;
} // HTTP响应正文类型
String contentType; if (uri.indexOf("html") !=-1 || uri.indexOf("html") !=-1) {
contentType="text/html";
} else if (uri.indexOf("jpg") !=-1 || uri.indexOf("jpeg") !=-1) {
contentType="image/jpeg";
} else if (uri.indexOf("gif") !=-1) {
contentType="image/gif";
} else
contentType="application/octet-stream"; /* 创建HTTP响应结果 */
String responseFirstLine="HTTP/1.1 200 OK\r\n";
String responseHeader="Content-Tyep:" + contentType + "\r\n\r\n";
InputStream in=HTTPServerParam.class.getResourceAsStream("test/" + uri);
OutputStream socketOut=socket.getOutputStream();
socketOut.write(responseFirstLine.getBytes());
socketOut.write(responseHeader.getBytes()); int len=0;
requestBuffer=new byte[128]; while ((len=in.read(requestBuffer)) !=-1) {
socketOut.write(requestBuffer, 0, len);
}
Thread.sleep(1000);
socket.close();
}
}
修改之后的HelloServerParam的基本逻辑就是如果客户端请求的URI位于servlet子目录下,就按照Serlvet来处理,否则就按照普通的静态文件来处理。当客户端请求访问特定的Servlet时,服务器端代码先从自己的servletCache缓存中寻找特定的Servlet实例,如果存在就调用它的service()方法;否则就先创建Servlet实例,把它放入servletCache缓存中,再调用它的service()方法。
如果学习过servlet的人就会发现,这其实就是实现了一个j2ee的servlet,现在相当于我们就自己建立一个非常简单的Tomcat服务器…当然这里只能说是一个转换器而已…不过基本的Tomcat基本的原理就是这些,希望能够帮助大家理解
1 安道尔 http://www.duana.ad/ 2 阿联酋 http://www.dubaicustoms.gov.ae/e ... n/Pages/Import.aspx 3 阿根廷 http://www.afip.gob.ar/aduanaDefault.asp 4 奥地利 https://english.bmf.gv.at/customs/Customs.html 5 澳大利亚 http://www.customs.gov.au/ 6 孟加拉 http://www.nbr-bd.org/customs.html 7 比利时 http://www.belgium.be/en/taxes/registration_duties/ 8 保加利亚 http://www.customs.bg/ 9 巴林 http://www.bahraincustoms.gov.bh/ 10 巴西 http://www.receita.fazenda.gov.br/Aduana/Importacao.htm 11 博茨瓦纳 http://www.finance.gov.bw/customs/index.htm 12 加拿大 http://www.cra-arc.gc.ca/menu-eng.html 13 瑞士 http://www.ezv.admin.ch/?lang=en 14 智利 http://www.aduana.cl/prontus_adu ... se/port/inicio.html 15 中国 http://www.customs.gov.cn/publish/portal0/ 16 哥伦比亚 http://www.dian.gov.co/contenidos/otros/consulta_de_arancel.html 17 哥斯达黎加 http://www.hacienda.go.cr/conten ... nacional-de-aduanas 18 塞浦路斯 http://www.mof.gov.cy/ 19 捷克 http://www.celnisprava.cz/en/Pages/default.aspx 20 德国 http://www.zoll.de/index.html 21 丹麦 http://www.skat.dk/ 22 厄瓜多尔 http://www.aduana.gob.ec/index.action 23 爱沙尼亚 http://www.emta.ee/ 24 埃及 http://www.customs.gov.eg/ 25 西班牙 http://www.agenciatributaria.es/ 26 芬兰 http://www.tulli.fi/en/finnish_customs/index.jsp 27 法国 http://www.douane.gouv.fr/ 28 英国 http://customs.hmrc.gov.uk/chann ... bel=pageImport_Home 29 格恩西岛 http://gov.gg/customsdutiesandrates 30 希腊 http://www.gsis.gr/ 31 危地马拉 http://portal.sat.gob.gt/sitio/ 32 中国香港 http://www.customs.gov.hk/pda/en/cargo_clearance/index.html 33 克罗地亚 http://www.carina.hr/Pocetna/IndexEN.aspx 34 匈牙利 http://en.nav.gov.hu/ 35 印度尼西亚 http://www.beacukai.go.id/wwwbcgoid/index.html 36 爱尔兰 http://www.revenue.ie/en/customs/index.html 37 以色列 http://ozar.mof.gov.il/ita2013/eng/mainpage.htm 38 印度 http://www.cbec.gov.in/cae1-english.htm 39 约旦 http://www.customs.gov.jo/english/default.shtm 40 日本 http://www.customs.go.jp/english/index.htm 41 韩国 http://www.customs.go.kr/kcshome ... ayoutSiteId=english 42 科威特 http://www.customs.gov.kw/en/Default.aspx 43 列支敦士登 http://www.llv.li/#/1974 44 斯里兰卡 http://www.customs.gov.lk/ 45 莱索托 http://www.lra.org.ls/Customs.php 46 立陶宛 http://www.cust.lt/ 47 卢森堡 http://www.do.etat.lu/ 48 拉脱维亚 http://www.vid.gov.lv/ 49 摩洛哥 http://www.douane.gov.ma/web/guest 50 摩纳哥 http://www.legimonaco.mc/305/leg ... 383b30!OpenDocument 51 中国澳门 http://www.customs.gov.mo/en/index1.htm 52 马耳他 http://finance.gov.mt/ 53 墨西哥 http://www.sat.gob.mx/sitio_internet/home.asp 54 马来西亚 http://www.customs.gov.my/ 55 纳米比亚 https://www.customs.gov.ng/ 56 尼日利亚 https://www.customs.gov.ng/ 57 荷兰 http://www.douane.nl/ 58 挪威 http://www.toll.no/default.aspx?id=3&epslanguage=en 59 新西兰 http://www.customs.govt.nz/Pages/default.aspx 60 阿曼 http://www.rop.gov.om/english/dg_customs.asp 61 巴拿马 http://www.ana.gob.pa/ 62 秘鲁 http://www.sunat.gob.pe/ 63 菲律宾 http://customs.gov.ph/ 64 巴基斯坦 http://www.cbr.gov.pk/ 65 波兰 http://www.mofnet.gov.pl/ 66 波多黎各 http://www.cbp.gov/ 67 葡萄牙 http://www.dgaiec.min-financas.pt/ 68 卡塔尔 http://www.customs.gov.qa/ 69 罗马尼亚 http://www.customs.ro/ 70 俄罗斯 http://eng.customs.ru/ 71 沙特阿拉伯 http://www.customs.gov.sa/Custom ... 20and%20Exportation 72 瑞典 http://www.tullverket.se/ 73 新加坡 http://www.customs.gov.sg/topNav/hom/index.html 74 斯洛文尼亚 http://www.carina.gov.si/ 75 斯洛伐克 http://www.colnasprava.sk/ 76 圣马力诺 http://www.cc.sm/default.asp?id=555 77 斯威士兰 http://www.sra.org.sz/index.php? ... d=59&Itemid=168 78 泰国 http://www.customs.go.th/wps/wcm ... /hs+system/hssystem 79 土耳其 http://www.gtb.gov.tr/ 80 美国 http://www.cbp.gov/ 81 委内瑞拉 http://www.seniat.gob.ve/portal/ ... O_SENIAT/04ADUANAS/ 82 越南 http://www.customs.gov.vn/English/Default.aspx 83 南非 http://www.sars.gov.za/
*请认真填写需求信息,我们会在24小时内与您取得联系。