Web全棧開發學習筆記—Part3 用NodeJS和Express寫服務端程式—b.把應用部署到網上

旭日東歌發表於2021-01-04

目錄

Same origin policy and CORS

Application to the Internet

Frontend production build

Serving static files from the backend

Streamlining deploying of the frontend

Proxy


接下來,將之前製作的前端連線到我們自己的後端。

前面的部分中,前端可以從作為後端的 json 伺服器向地址 http://localhost:3001/notes 索取便箋列表。

現在後端有一個稍微不同的 url 結構,便箋可以從 http://localhost:3001/api/notes 中獲取到。

修改 src/services/notes.js 中的baseUrl屬性 :

import axios from 'axios'
const baseUrl = 'http://localhost:3001/api/notes'
const getAll = () => {
  const request = axios.get(baseUrl)
  return request.then(response => response.data)
}

// ...

export default { getAll, create, update }

現在前端的 GET 請求由於某些原因不能工作: http://localhost:3001/api/notes:

fullstack content

但是可以從瀏覽器和Postman訪問後端,沒有任何問題。

 

Same origin policy and CORS

【同源政策和 CORS】

問題出在一個叫 CORS 的東西上,或者叫跨來源資源共享。

根據維基百科 :

Cross-origin resource sharing (CORS) is a mechanism that allows restricted resources (e.g. fonts) on a web page to be requested from another domain outside the domain from which the first resource was served. A web page may freely embed cross-origin images, stylesheets, scripts, iframes, and videos. Certain "cross-domain" requests, notably Ajax requests, are forbidden by default by the same-origin security policy. 

Cross-origin resource sharing (CORS)是一種機制,它允許一個網頁上受限制的資源(例如字型),從提供一手資源的域名以外的另一個域名請求跨來源資源共享。 一個網頁可以自由地嵌入跨來源的圖片、樣式表、指令碼、 iframe 和視訊。 預設情況下,同源安全策略禁止某些“跨域”請求,特別是 Ajax 請求。

這裡問題在於,預設情況下,執行在瀏覽器應用的 JavaScript 程式碼只能與相同的伺服器通訊。

因為伺服器位於本地主機埠3001,前端位於本地主機埠3000,所以它們不具有相同的源。

同源策略和 CORS 並不是特定於 React 或 Node 的,它實際上是 web 應用操作的通用原則。

我們可以通過使用 Node 的 cors 中介軟體來允許來自其他源的請求。

安裝cors

npm install cors

使用中介軟體並允許來自所有來源的請求:

const cors = require('cors')

app.use(cors())

現在前端工作正常了!但是,在後端還沒有實現更改便箋重要性的功能。

 

Application to the Internet

【將應用部署到網上】

現在整個棧已經準備就緒,現在將應用遷移到網際網路上。 我們將使用古老的 Heroku https://www.Heroku.com 。

如果您以前從未使用過 Heroku,您可以從Heroku 文件或通過谷歌搜尋找到指令。

向專案的根目錄新增一個名為 Procfile的檔案,告訴 Heroku 如何啟動應用。

web: npm start

更改應用在index.js 檔案底部使用的埠定義,如下所示:

const PORT = process.env.PORT || 3001app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`)
})

現在我們使用定義在環境變數的埠,如果環境變數 PORT 是未定義的,則使用埠3001。

Heroku 會在環境變數的基礎上配置應用埠。

在專案目錄中建立一個 Git 倉庫,並使用如下內容新增 .gitignore

node_modules

使用命令heroku create建立一個 Heroku 應用,將你的程式碼提交到倉庫並將其推送到Heroku,git push Heroku main

如果一切順利,應用就能正常工作:

fullstack content

如果沒有執行成功,可以通過使用命令heroku logs 讀取 heroku logs 來發現問題。

前端也與 Heroku 的後端一起工作。 可以將前端的後端地址更改為後端在 Heroku 的地址http://localhost:3001

 

Frontend production build

【前端生產構建】

到目前為止,我們一直在開發模式 中執行 React code。

開發模式下,應用提供清晰的錯誤訊息,立即向瀏覽器渲染程式碼更改,等等。

部署應用時,需要建立一個生產構建或一個為生產而優化的應用版本。

使用create-react-app 建立的應用的生產構建可以使用命令npm run build建立。

從前端專案的根目錄執行這個命令,將建立一個名為build 的目錄(其中包含應用中唯一的 HTML 檔案index. HTML) ,其中包含目錄static

應用的 JavaScript 程式碼的Minified版本將生成到static 目錄。 即使應用程式碼位於多個檔案中,所有的 JavaScript 都將被縮小到一個檔案中。 應用依賴項的所有程式碼也將縮小到這個單一檔案中。

縮小後的程式碼可讀性不是很好,開頭是這樣的:

!function(e){function r(r){for(var n,f,i=r[0],l=r[1],a=r[2],c=0,s=[];c<i.length;c++)f=i[c],o[f]&&s.push(o[f][0]),o[f]=0;for(n in l)Object.prototype.hasOwnProperty.call(l,n)&&(e[n]=l[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,a||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var l=t[i];0!==o[l]&&(n=!1)}n&&(u.splice(r--,1),e=f(f.s=t[0]))}return e}var n={},o={2:0},u=[];function f(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.m=e,f.c=n,f.d=function(e,r,t){f.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},f.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})

 

Serving static files from the backend

【從後端服務部署靜態檔案】

部署前端可以將生產構建( build 目錄)複製到後端倉庫的根目錄,並配置後端以顯示前端的 main page (檔案 build/index.html)作為其主頁。

我們從將前端的生產構建複製到後端的根目錄。 使用一臺Mac 或 Linux 計算機,可以通過命令從前端目錄進行復制

cp -r build ../../../osa3/notes-backend

如果你使用的Windows作業系統,你可以使用copy 或者 xcopy 命令。要麼就簡單地使用複製貼上即可。

後端目錄現在應該如下所示:

fullstack content

為了讓 express 顯示 static content、 頁面 index.html 和它用來fetch的 JavaScript 等等,我們需要一個來自 express 的內建中介軟體,稱為static

在中介軟體宣告中新增如下內容時

app.use(express.static('build'))

每當 express 收到一個 HTTP GET 請求時,它都會首先檢查build 目錄是否包含與請求地址對應的檔案。 如果找到正確的檔案,express 將返回該檔案。

現在 HTTP GET 向地址www.serversaddress.com/index.html www.serversaddress.com 的GET請求,將顯示 React 前端。 Get 請求到地址 www.serversaddress.com/notes 將由後端程式碼處理。

因為現在的情況下,前端和後端都在同一個地址,所以可以宣告 baseUrl 為relative URL,省略宣告伺服器的部分。

import axios from 'axios'
const baseUrl = '/api/notes'
const getAll = () => {
  const request = axios.get(baseUrl)
  return request.then(response => response.data)
}

// ...

更改之後,我們必須建立一個新的生產構建,並將其複製到後端儲存庫的根。

該應用現在可以從後端 地址 http://localhost:3001 中使用:

fullstack content

我們的應用現在的工作方式與我們在第0章節中研究的單頁應用 示例應用完全一樣。

當我們使用瀏覽器訪問地址 http://localhost:3001 時,伺服器從build 倉庫返回index. html 檔案。 檔案的摘要內容如下:

<head>
  <meta charset="utf-8"/>
  <title>React App</title>
  <link href="/static/css/main.f9a47af2.chunk.css" rel="stylesheet">
</head>
<body>
  <div id="root"></div>
  <script src="/static/js/1.578f4ea1.chunk.js"></script>
  <script src="/static/js/main.104ca08d.chunk.js"></script>
</body>
</html>

該檔案包含一些指令,用於獲取定義應用樣式的 CSS 樣式表,以及兩個script 標籤,這些標記說明瀏覽器獲取應用的 JavaScript 程式碼——即實際的 React 應用。

React程式碼從伺服器地址 http://localhost:3001/api/notes 獲取便箋,並將它們渲染到螢幕上。 伺服器和瀏覽器之間的通訊可以在開發控制檯的Network 選項卡中看到:

fullstack content

確保應用的生產版本在本地正常工作之後,將前端的生產構建提交到後端儲存庫,並將程式碼再次推送到 Heroku。

現在還沒有新增改變後端便箋重要性的功能。

fullstack content

應用現在將便箋儲存到一個變數中。 如果應用崩潰或重新啟動,所有資料都將消失。

因此應用需要一個資料庫。

 

Streamlining deploying of the frontend

【流程化前端部署】

為了簡化建立前端的新的生產構建,我們在後端儲存庫的package.json 中新增一些 npm-scripts:

{
  "scripts": {
     //...
    "build:ui": "rm -rf build && cd ../../osa2/materiaali/notes-new && npm run build --prod && cp -r build ../../../osa3/notes-backend/",
    "deploy": "git push heroku main",
    "deploy:full": "npm run build:ui && git add . && git commit -m uibuild && npm run deploy",    
    "logs:prod": "heroku logs --tail"
  }
}

指令碼 npm run build:ui用於構建前端,並在後端儲存庫下複製生產版本。npm run deploy 會將當前的後端版本釋出到heroku.

npm run deploy:full 會將這兩者結合起來,幷包含更新後端儲存庫所需的git 命令。

還有一個指令碼 npm run logs:prod 用於顯示 heroku 日誌。

我構建的指令碼中的目錄路徑 build:ui 依賴於檔案系統中儲存庫的位置。

在Windows中,npm 指令碼預設是執行在cmd.exe 這個預設的shell中的,而它並不支援bash命令。因此如果希望以上的bash命令運轉良好,可以將預設的shell換成bash(預設Windows安裝Git時已經安裝了Bash):

 

Proxy

【代理】

前端上的更改導致它不能再在開發模式下工作(當使用命令 npm start 啟動時) ,因為到後端的連線無法工作。

fullstack content

這是由於將後端地址更改為了一個相對 URL:

const baseUrl = '/api/notes'

因為在開發模式下,前端位於地址localhost: 3000,所以對後端的請求會傳送到錯誤的地址localhost:3000/api/notes。 而後端位於localhost: 3001

如果這個專案是用 create-react-app 建立的,將如下宣告新增到前端倉庫的package.json 檔案中就足夠了。

{
  "dependencies": {
    // ...
  },
  "scripts": {
    // ...
  },
  "proxy": "http://localhost:3001"}

在重新啟動之後,React 開發環境將作為一個代理工作。 如果 React 程式碼對伺服器地址http://localhost:3000發出了一個 HTTP 請求,而不是 React 應用本身管理的地址(即當請求不是為了獲取應用的 CSS 或 JavaScript) ,那麼該請求將被重定向到 HTTP://localhost:3001 的伺服器。

現在前端可以在開發和生產模式下與伺服器一起工作。

現在的方法部署新版本需要生成新的前端生產構建並將其複製到後端儲存庫。 這使得建立一個自動化的部署管道變得更加困難。 部署管道是指通過不同的測試和質量檢查將程式碼從開發人員的計算機轉移到生產環境的自動化控制的方法。有多種方法可以實現這一點(例如將後端和前端程式碼放到同一倉庫中) 。

相關文章