本文面向已經有一些編程基礎(會至少一門編程語言,比如python),但是沒有搭建過web應用的人群,會寫得盡量細致。重點介紹流程和部署云端的步驟,具體javascript代碼怎么寫之類的,這里不會涉及。
搭建網站分為兩步:首先在本地搭建網站,測試可以運行;然后把網站移動到服務器,讓大家都可以訪問。
本地搭建網站
首先了解web程序的運行邏輯:
- 用戶通過瀏覽器輸入網址(URL)
- 瀏覽器通過域名系統(DNS)將用戶輸入的網址轉換為服務器的 IP 地址
- 瀏覽器根據解析到的 IP 地址,向相應的服務器發起請求
- 服務器接收到請求后,返回結果(這個結果可能是一個html網頁,或者一張圖片,或者一段文本,之類的)
- 瀏覽器收到服務器響應后,開始解析 HTML、CSS 和 JavaScript 等文件(HTML 定義網頁結構,CSS 控制網頁樣式,JavaScript 負責動態交互和功能實現)
- 瀏覽器將解析后的內容渲染并展示給用戶
要先搭建一個可以在本地運行的web程序,然后再把它部署到云端,讓大家都可以訪問。我以手頭的項目為例,講解web網站的搭建過程。
設計網站界面和功能
這個web網站的功能是這樣的:它有兩個頁面,問詢方和回答方。
- 問詢方可以拍攝照片或者錄制視頻,針對拍攝到的內容提出問題,然后把拍攝到的內容發送給回答方;
- 發送之后,回答方會收到圖片或視頻,然后回答方輸入答案的文本,點擊發送;
- 然后問詢方收到答案,并且在無障礙模式下可以自動朗讀出來
我設計的問詢方網頁如下(問詢方在手機端使用):上面四個按鈕,下面一行文本顯示答案。
回答方的網頁如下(回答方在pc端使用):上面顯示視頻(圖片),下面輸入框可以輸入文本,然后一個發送按鈕
這部分的代碼會上傳到 github
根據設計的網頁,寫前端代碼
根據設計的界面,使用 html 和 css 和 javascript 把前端寫好(HTML 定義網頁內容,有哪些文字、圖片之類的;CSS 控制網頁樣式,字體多大、按鈕顏色、大小之類的;JavaScript 負責動態交互和功能實現,例如點下按鈕之后拍攝照片,之類的功能);這些都比較簡單,實在不行你問問 ai 也能幫你搞定大部分。
這部分代碼貼在這里非常累贅,所以就不貼了;如果有需求可以去找github上的倉庫。
根據設計的功能,寫后端代碼
選擇你熟悉的后端語言,這里選擇 python;
然后選擇合適的框架,python 寫的 web 框架有 Django、Flask等,因為這是個小項目,所以這里選擇 python;
另外,根據設計的網站功能,這里要實現雙向通信(就是一方發送消息之后,另一方要實時收到該信息),在后端用 flask_socketio
這個包;在前端用 <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.min.js"></script>
Flask 入門的教程非常多,這里不贅述了。
需要強調的地方:因為這里用 Socket.io,在 javascript 里面,本地部署的時候是這么寫的:
var socket = io.connect('http://127.0.0.1:8099');
但是部署到服務器的時候,需要修改:
const socket = io.connect('https://你的域名.com');
不然實時通信通不了的!
把網站部署到服務器
要把能夠在本地運行的程序部署到云端,需要:
- 一個云服務器,一般是租
- 一個公網ip,一般是租一個彈性公網ip
- 一個備份過的域名,備份流程大約是半個月左右,建議盡早準備
這里以阿里云服務器租賃為例:
租好服務器之后,進入服務器控制臺,選擇“安全組”選項卡,選擇操作中的“管理規則”,選擇手動添加,添加常用端口,例如:
- 80端口 - 用于HTTP(超文本傳輸協議)通信,即普通的Web瀏覽。
- 443端口 - 用于HTTPS(HTTP Secure)通信,即安全的Web瀏覽,通過SSL/TLS加密。
- 22端口 - 用于SSH(安全殼協議),用于安全登錄到遠程服務器。
- 53端口 - 用于DNS(域名系統),負責將域名解析為IP地址。
- 自己的web程序使用的端口
然后登陸到服務器,把本地文件上傳到服務器,然后開始部署程序。
首先要學習一下部署web程序需要的軟件:反向代理和WSGI服務器。它們具體是什么作用呢?
用戶的請求通過互聯網到達你的云服務器之后:
- 首先通過反向代理服務器(通常是nginx)接收用戶的請求
- 然后 nginx 將動態請求轉發給WSGI(Web Server Gateway Interface)服務器(對于python后端的程序而言,常用的有Gunicorn、uWSGI、Waitress等,這里用的是簡單的Gunicorn);Nginx 會添加一些請求頭信息(例如 Host、X-Real-IP),以便 Gunicorn 和 Flask 能夠正確處理請求
- Gunicorn 接收到 Nginx 轉發的請求后,根據請求的 URL 和方法(GET、POST 等),調用 Flask 應用中對應的路由函數
- Flask 程序處理請求,并生成響應,將生成的響應(HTML、JSON 或其他數據)返回給 Gunicorn
- Gunicorn 將 Flask 生成的響應(包括狀態碼、響應頭和響應體)返回給 Nginx
- Nginx 將最終的響應返回給用戶的瀏覽器
此處Nginx的作用:
- 負載均衡:
- 將請求分發到多個后端服務器,提升系統性能和可靠性。(但一般個人網站都只有一個后端服務器啦)
- 安全性:
- 隱藏后端服務器的真實 IP 地址,防止直接攻擊。
- 提供 SSL 終止,加密客戶端與反向代理之間的通信。
- 靜態文件處理:
- 直接處理靜態文件請求,減輕后端服務器的負擔。
- 緩存:
- 緩存常用資源,減少后端服務器的負載,提升響應速度。
此處 WSGI 服務器的作用:Python Web 應用與 Web 服務器之間的標準接口。它定義了 Web 服務器如何將請求傳遞給 Python Web 應用,以及應用如何返回響應。此時,WSGI 服務器作為中間層,連接 Web 服務器(如 Nginx)和 Python Web 應用。
所以我們先安裝和配置 nginx,然后再安裝和配置 WSGI(這里選擇 Gunicorn)
安裝和配置 nginx
請看保姆教程:
Linux系統下安裝配置nginx(保姆級教程)
Linux系統下安裝配置 Nginx 超詳細圖文教程
這里主要是配置比較難,因為該項目用了 Socket.io,而且還有服務器網址和本地網址傻傻分不清楚。Nginx 的配置文件通常位于 /etc/nginx/nginx.conf
或 /etc/nginx/sites-available/
路徑下面。這里貼上我的配置文件:
server {# listen 80 default_server;# listen [::]:80 default_server;# listen 80;listen 443 ssl; # 因為我這要訪問攝像頭,所以需要 https 協議,監聽443端口,并且安裝 ssl 證書server_name yourDomainName.com; # 換成你的域名,要通過實名備份才能訪問ssl_certificate /etc/nginx/cert/pem文件; # 替換成你申請的ssl證書,把證書放在這個地址下面。這個文件夾是你自己創建的。ssl_certificate_key /etc/nginx/cert/key文件; # 同上ssl_session_cache shared:SSL:10m;ssl_session_timeout 4h;ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; # 設置加密套件ssl_protocols TLSv1 TLSv1.1 TLSv1.2;ssl_prefer_server_ciphers on;client_max_body_size 100m; # 這個參數需要調整,因為我要傳視頻,如果這個參數比較小,長一點點的視頻就傳不上來# SSL configuration## listen 443 ssl default_server;# listen [::]:443 ssl default_server;## Note: You should disable gzip for SSL traffic.# See: https://bugs.debian.org/773332## Read up on ssl_ciphers to ensure a secure configuration.# See: https://bugs.debian.org/765782## Self signed certs generated by the ssl-cert package# Don't use them in a production server!## include snippets/snakeoil.conf;# root /var/www/html;# Add index.php to the list if you are using PHP# index index.html index.htm index.nginx-debian.html;# server_name _;location / {proxy_pass http://0.0.0.0:8099; # 將匹配到的請求轉發給指定的后端服務器,該參數指定后端服務器的地址,這個參數很重要proxy_redirect off;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;# 解決跨域問題add_header Access-Control-Allow-Origin *; #解決跨域問題add_header Access-Control-Allow-Methods 'GET,POST';add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,Origin,Accept'; #解決跨域問題# proxy_set_header X-Forwarded-Proto $scheme; # fix flask redirect生產環境 從https到http跳轉# First attempt to serve request as file, then# as directory, then fall back to displaying a 404.# try_files $uri $uri/ =404;}#用Nginx訪問Flask靜態文件 #靜態文件在static的子目錄或更低層的子目錄中location /static/(.*) {root /home/eye_help_flask/; # 這里的路徑是絕對路徑,xxx是指static目錄的上級目錄,一般是網站根目錄}location /wss/ { proxy_pass http://0.0.0.0:8099/; #通過配置端口指向部署websocker的項目proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; proxy_set_header X-real-ip $remote_addr;proxy_set_header X-Forwarded-For $remote_addr;}# 因為該程序用了 Socket.IO 雙向通信,所以得配置這個location /socket.io/ {proxy_set_header Origin '';proxy_pass http://127.0.0.1:8099/socket.io/;proxy_http_version 1.1; # WebSocket 需要 HTTP/1.1 協議proxy_set_header Host $host; # 傳遞客戶端請求的 Host 頭。proxy_set_header X-Real-IP $remote_addr;proxy_set_header Upgrade $http_upgrade; # 將請求頭 Upgrade 設置為 websocket,表示協議升級。proxy_set_header Connection "upgrade"; # 將請求頭 Connection 設置為 upgrade,表示連接升級。proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_read_timeout 600s;}
}
解釋一些關鍵的參數,這些參數配錯了會很麻煩:
location /path/ {proxy_pass http://backend_server} : proxy_pass
用于將匹配到的請求轉發給指定的后端服務器。它是 Nginx 反向代理功能的核心配置之一。其中location /path/
是匹配請求的 URL 路徑,http://backend_server
:指定后端服務器的地址。協議與后端使用的保持一致,后端用https
這里也用https
,反之亦然。這里填的是http://0.0.0.0:8099
,其中0.0.0.0
表示監聽所有可用的網絡接口(即所有 IP 地址),如果你的后端服務器(如 Gunicorn)運行在同一臺機器上,通常會綁定到 0.0.0.0
,以便 Nginx 可以通過本地網絡訪問它。如果后端服務運行在其他服務器上,可以使用外部 IP 或域名,例如http://192.168.1.100:8080;
。8099
是端口號,因為我的 Gunicorn 設置監聽該端口,所以填這個。如果你的不是監聽該端口,要修改該參數,不然會出錯的。Nginx 需要通過這個端口與后端服務器通信。
另外,這里的尾部斜杠也是有影響的。proxy_pass
的 URL 是否以斜杠結尾會影響 Nginx 如何轉發請求路徑。具體規則如下:
proxy_pass
的 URL 以斜杠結尾,Nginx 會將 location 匹配的路徑部分去掉,然后將剩余部分附加到proxy_pass
的 URL 后。- 例如
location /api/ {proxy_pass http://backend-server/;}
- 請求:
http://yourdomain.com/api/user
- 轉發:
http://backend-server/user
(/api/
被去掉)
- 例如
proxy_pass
的 URL 不以斜杠結尾,Nginx 會將 location 匹配的完整路徑附加到proxy_pass
的 URL 后。- 例如
location /api/ {proxy_pass http://backend-server;}
- 請求:
http://yourdomain.com/api/user
- 轉發:
http://backend-server/api/user
(/api/
被保留)
- 例如
如何判斷后端服務使用的是 HTTP 還是 HTTPS?
如果你有后端服務的訪問權限,可以直接查看其配置文件或啟動命令。例如,Gunicorn 的啟動命令:gunicorn -b 0.0.0.0:8000 your_app:app
默認使用 HTTP。如果使用 HTTPS,通常會指定證書和密鑰:gunicorn -b 0.0.0.0:8000 --keyfile key.pem --certfile cert.pem your_app:app
最后,配置好之后記得重啟一下 nginx!
安裝和配置 Gunicorn
請看保姆教程:Gunicorn簡介、安裝、配置、啟動
這里我的配置文件如下:
bind = '0.0.0.0:8099'
user = 'root'
workers = 1
threads = 1
daemon = True
backlog = 512
worker_class = 'eventlet'
chdir = '/home/eye_help_flask'
access_log_format = '%(t)s %(p)s %(h)s "%(r)s" %(s)s %(L)s %(b)s %(f)s" "%(a)s"'
loglevel = 'info'
errorlog = chdir + '/logs/error.log'
accesslog = chdir + '/logs/access.log'
pidfile = chdir + '/logs/ddq.pid'
我的啟動命令如下:
gunicorn -c gunicory_conf.py app:app
重啟 gunicorn命令如下:
- 通過執行如下命令,可以獲取Gunicorn進程樹:
pstree -ap|grep gunicorn
- 重啟Gunicorn任務
kill -HUP 主進程號
遇到過的一些奇怪 bug
- iOS 攝像頭畫面黑屏:在 video 的屬性上加上 playsinline
- 調用
Navigator.vibrate()
但沒反應:ios不許而且Safari會有奇怪的錯誤 - ios 自動播放聲音失效:因為 ios 不許,別想了,換一個方法吧
- 點拍攝照片,用 createObjectUrl 生成前綴為 blob 的 url ,圖片無法顯示,報錯為 net::ERR_FILE_NOT_FOUND
在 edge 瀏覽器內拍照,另一個頁面可以顯示;但是在 Google 瀏覽器的就無法顯示。查到 圖片路徑前綴有blob?圖片渲染不出來? 說如果數據庫的圖片地址前綴帶有blob://,那么可能是因為在上傳圖片時,使用了Blob URL來表示圖片。在JavaScript中,Blob URL是通過URL.createObjectURL(blob)方法創建的,其格式為blob://加上一個由瀏覽器自動生成的32位十六進制的字符串。用Blob URL表示的圖片是存儲在瀏覽器的內存中,而不是存儲在硬盤上,所以當你在另一個頁面中打開這個URL時,就無法訪問該圖片。