整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          JavaScript 的這個難點,毀掉了多少程序員?

          JavaScript 的這個難點,毀掉了多少程序員?

          CSDN編者按】本文將通過簡單的術語和真實世界的例子解釋 JavaScript 中 this 及其用途,并告訴你寫出好的代碼為何如此重要。

          1.this 適合你嗎?

          我看到許多文章在介紹 JavaScript 的 this 時都會假設你學過某種面向對象的編程語言,比如 Java、C++ 或 Python 等。但這篇文章面向的讀者是那些不知道 this 是什么的人。我盡量不用任何術語來解釋 this 是什么,以及 this 的用法。

          也許你一直不敢解開 this 的秘密,因為它看起來挺奇怪也挺嚇人的。或許你只在 StackOverflow 說你需要用它的時候(比如在 React 里實現某個功能)才會使用。

          在深入介紹 this 之前,我們首先需要理解函數式編程和面向對象編程之間的區別。

          2.函數式編程 vs 面向對象編程

          你可能不知道,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 獲得的。現在需要將其轉換成方便你的項目使用的格式。我們假設你想顯示的好友信息如下:

          • 姓名,格式為`${firstName} ${lastName}`
          • 三篇隨機文章
          • 距離生日的天數

          3.函數式方式

          函數式的方式就是將整個數組或者數組中的某個元素傳遞給某個函數,然后返回你需要的信息:

          const fullNames=getFullNames(data)
          // ['Ross, Bob', 'Smith, Joanna', ...]
          

          首先我們有 Facebook API 返回的原始數據。為了將其轉換成需要的格式,首先要將數據傳遞給一個函數,函數的輸出是(或者包含)經過修改的數據,這些數據可以在應用中向用戶展示。

          我們可以用類似的方法獲得隨機三篇文章,并且計算距離好友生日的天數。

          函數式的方式是:將原始數據傳遞給一個函數或者多個函數,獲得對你的項目有用的數據格式。

          4.面向對象的方式

          對于編程初學者和 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
          

          面向對象的方式就是為數據創建對象,每個對象都有自己的狀態,并且包含必要的信息,能夠生成需要的數據。

          5.這跟 this 有什么關系?

          你也許從來沒想過要寫上面的 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 出場了!

          6.終于——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 不存在。似乎我們沒什么辦法了。

          輪到綁定上場了!

          7.明確綁定 this

          我們需要明確地將 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 實際上是箭頭函數。

          8.箭頭函數

          你也許注意到了箭頭函數最近很流行。人們喜歡箭頭函數,因為很簡潔、很優雅。而且你還知道箭頭函數和普通函數有點區別,盡管不太清楚具體區別是什么。

          簡而言之,兩者的區別在于:

          在定義箭頭函數時,不管 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 也指向對象本身,這正是我們需要的結果。

          9.結論

          this 有時很不好理解,但它對于開發 JavaScript 應用非常有用。本文當然沒能介紹 this 的所有方面。一些沒有涉及到的話題包括:

          • call 和 apply;
          • 使用 new 時 this 會怎樣;
          • 在 ES6 的 class 中 this 會怎樣。

          我建議你首先問問自己在這些情況下的 this,然后在瀏覽器中執行代碼來檢驗你的結果。

          想學習更多關 于this 的內容,可參考《你不知道的 JS:this 和對象原型》:

          • https://github.com/getify/You-Dont-Know-JS/tree/master/this%20%26%20object%20prototypes

          如果你想測試自己的知識,可參考《你不知道的JS練習:this和對象原型》:

          • https://ydkjs-exercises.com/this-object-prototypes

          原文: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的運行原理,基本上每個人都上網,但是既然我們自己在學習在做動態網頁,有沒有真正考慮過我們在瀏覽網頁時底層的一些基本運行原理。



          這中間其實是你的客戶端瀏覽器與服務器端的通信過程,具體如下:

          1. 瀏覽器與網絡上的域名為www.yingside.com 的 Web服務器建立TCP連接
          2. 瀏覽器發出要求訪問JAVA/index.html的HTTP請求
          3. Web服務器在接收到HTTP請求后,解析HTTP請求,然后發回包含index.html文件數據的HTTP響應
          4. 瀏覽器接受到HTTP響應后,解析HTTP響應,并在其窗口中展示index.html文件
          5. 瀏覽器與Web服務器之間的TCP連接關閉

          就是這樣的一個簡單過程,這個過程很多書上也有,還有圖。但是,就是這個樣子的一個過程,中間就有很多值得探討的地方。

          我們來解析一下,從上面這個過程中分析出

          瀏覽器應該有的功能

          1. 請求與Web服務器建立TCP連接
          2. 創建并發送HTTP請求
          3. 接受并解析HTTP響應
          4. 展示html文檔

          Web服務器應該具有的功能

          1. 接受來自瀏覽器的TCP的請求
          2. 接收并解析HTTP請求
          3. 創建并發送HTTP響應

          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請求由三部分組成

          1. 請求方法,URI和HTTP協議的版本
          2. 請求頭(Request Header)
          3. 請求正文(Request Content)

          看一個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部分組成

          1. HTTP協議的版本,狀態代碼和描述
          2. 響應頭(Response Header)
          3. 響應正文(Response Content)
          4. 看一個HTTP響應列子:
          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/
          

          主站蜘蛛池模板: 精品无码一区二区三区水蜜桃| 中日韩一区二区三区| 国模无码视频一区二区三区| 国产一区高清视频| 精品无码日韩一区二区三区不卡 | 久久久久人妻一区精品果冻| 久久久久一区二区三区| 国产一区二区精品在线观看| 精品香蕉一区二区三区| 无码人妻一区二区三区在线| 久久精品无码一区二区日韩AV| 精品无码国产AV一区二区三区| 99精品一区二区免费视频| 国产在线一区二区| 99久久国产精品免费一区二区| 久久影院亚洲一区| 国偷自产Av一区二区三区吞精| 亚洲日本一区二区一本一道 | 成人精品一区二区激情| 人妻免费一区二区三区最新| 天码av无码一区二区三区四区 | 日本一区二区三区在线网 | 一区二区三区免费精品视频| 人妻精品无码一区二区三区| 亚洲综合一区二区三区四区五区| 日韩高清国产一区在线| 国产一区二区三区手机在线观看| 国产精品美女一区二区视频| 久久久久人妻精品一区| 国内精品无码一区二区三区| 青青青国产精品一区二区| 欲色aV无码一区二区人妻| 一级特黄性色生活片一区二区| 亚洲日本中文字幕一区二区三区| 麻豆aⅴ精品无码一区二区| 亚洲日韩国产一区二区三区在线 | 97久久精品一区二区三区| 国产丝袜视频一区二区三区| 亚洲AV无码一区二区三区系列| 夜夜嗨AV一区二区三区| 一色一伦一区二区三区|