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小時內與您取得聯系。