第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

為了賬號安全,請及時綁定郵箱和手機立即綁定
1. 前言

本章節(jié)將和大家一起聊聊 Spring MVC 是如何處理異常的。Spring MVC 框架中,將異常處理從業(yè)務邏輯中解耦出來,既保證了業(yè)務邏輯高度內聚性,又能實現了異常信息的統(tǒng)一處理和維護。很優(yōu)雅的解決了異常的問題。通過本章節(jié)內容的學習,你將了解到 Spring MVC 內置的異常處理機制。將異常映射成 HTTP 狀態(tài)碼;全局異常處理器的使用。這個將是本章節(jié)的重點;與異常有關的注解。

2.1 資源 Resource

REST 的名稱 “表述性狀態(tài)轉移” 中,省略了主語。REST 的全稱應該是 “資源表述性狀態(tài)轉移”。所謂 “資源”,就是網絡上的一個實體,或者說是網絡上的一個具體信息。面是一些資源的例子:它可以是一段文本、一張圖片、一首歌曲、一種服務,總之就是一個具體的實在,下一張圖片一個視頻一篇文章一篇回復使用統(tǒng)一資源定位符 URI 指向資源,對應一個特定的 URI。通過 URI 要獲取這個資源,因此 URI 就成了每一個資源的地址或獨一無二的識別符。下面以論壇為例,給出資源和相關 URI:資源URI主題http://www.bbs.com/topics/123回復http://www.bbs.com/anwers/456論壇中存在有多個主題 (topic),每個主題有一個唯一的 id,在這個例子中 /topics/123 標識了一個主題;每個主題下存在相應的回復 (answer),每個回復有一個唯一的 id,在這個例子中 /answers/123 標識了一個回復。對每一種資源,可以進行增加、修改、刪除的操作,在這個例子中,即為增加主題、修改主題、刪除主題。

3.2 同步「Token」模式

這種方法時防御「CSRF」攻擊的最常用手段,它的原理是確保項目表網站發(fā)送的請求中,每次都包含一個被稱為「CSRF Token」的隨機參數。每當請求提交到服務端后,服務端會對比請求中包含的「CSRF Token」和期望的是否匹配,如果不匹配,此請求作廢。這里的關鍵是,「CSRF Token」不能由瀏覽器生成。來看一下代碼:<form method="post" action="/transfer"> <!-- csrf 頭 --> <input type="hidden" name="_csrf" value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/> <!-- 匯款金額 --> <input type="text" name="amount"/> <!-- 匯款路由號 --> <input type="text" name="routingNumber"/> <!-- 匯款賬戶 --> <input type="text" name="account"/> <input type="submit" value="提交"/></form>上述代碼中增加了 _csrf 參數,且該值由服務器生成并埋在頁面表單中,此時 HTTP 請求的內容如下:POST /transfer HTTP/1.1Host: bank.example.comCookie: JSESSIONID=randomidContent-Type: application/x-www-form-urlencodedamount=100.00&routingNumber=1234&account=9876&_csrf=4bfd1575-3ad1-4d21-96c7-4ef2d9f86721由于瀏覽器同源策略,我們不用擔心惡意網站可以得到 _csrf 的值,而且惡意網站無法偽造出于服務器想匹配的「CSRF Token」,這樣就保護了用戶信息的安全。

5. 小結

錯誤碼是接口文檔中很重要的一部分,它是 Http 協議碼的補充,錯誤碼并沒有固定的格式要求,但是一般由 業(yè)務模塊+模塊下的詳細錯誤信息 組成。此外,還需要根據場景定義錯誤場景下的消息提示符,可以根據客戶端的語言返回相應語言的提示符。給用戶的錯誤信息中也盡量避免過多展示底層實現細節(jié),減小系統(tǒng)風險暴露。

1.排序的使用

在類視圖中設置 filter_backends,使用rest_framework.filters.OrderingFilter過濾器,REST framework 會在請求的查詢字符串參數中檢查是否包含了 ordering 參數,如果包含了 ordering 參數,則按照 ordering 參數指明的排序字段對數據集進行排序。前端可以傳遞的 ordering 參數的可選字段值需要在 ordering_fields 中指明。示例:class StudentViewSet(ModelViewSet): queryset = StudentsModel.objects.all() serializer_class = StudentsSerializer filter_backends = [OrderingFilter] ordering_fields = ('id', 's_age', 's_number')此時,訪問 http://127.0.0.1:8000/api/students/?ordering=-s_age ,服務器返回按年齡逆序排序后的學生信息。

3.2 編寫列表布局

接著就是要針對 ListView 編寫一個列表樣式了,學過 ListView 的同學想必都不在話下,如果對 ListView 不熟悉的可以回顧一下第 24 節(jié)的內容,這里我們新建 list_item.xml 文件,用最經典的 TextView 搭配 ImageView 的方式:<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="100dp" android:gravity="center" android:orientation="horizontal" android:background="?android:attr/activatedBackgroundIndicator" android:minHeight="?android:attr/listPreferredItemHeightSmall" android:padding="10dp" > <ImageView android:id="@+id/imageViewIcon" android:layout_width="100dp" android:layout_height="wrap_content" android:paddingRight="10dp" /> <TextView android:id="@+id/textViewName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="15dp" android:textColor="@android:color/black" android:textAppearance="?android:attr/textAppearanceListItemSmall" /></LinearLayout>

3.1 布局文件

首先我們來通過 xml 編寫布局文件,根據上面的需求要在 ViewFlipper 中放入 3 個布局。我們用一個 RelativeLayout 作為一個 ViewFlipper 的子 View 占滿一屏內容,只需要包含三個這樣的 RelativeLayout 即可,代碼如下:<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:showIn="@layout/activity_main"> <ViewFlipper android:id="@+id/view_flipper" android:layout_width="fill_parent" android:layout_height="fill_parent" android:inAnimation="@anim/in_from_right" android:outAnimation="@anim/out_from_left"> <RelativeLayout android:layout_width="fill_parent" android:layout_height="fill_parent"> <ImageView android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_gravity="center" android:adjustViewBounds="true" android:background="@android:color/black" android:scaleType="centerCrop" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:gravity="center" android:text="第一屏:好好學Android" android:textColor="@android:color/white" android:textSize="18dp" android:textStyle="bold" /> </RelativeLayout> <RelativeLayout android:layout_width="fill_parent" android:layout_height="fill_parent"> <ImageView android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_gravity="center" android:adjustViewBounds="true" android:background="@android:color/darker_gray" android:scaleType="centerCrop" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:gravity="center" android:text="第二屏:在慕課網好好學Android" android:textSize="18dp" android:textStyle="bold" /> </RelativeLayout> <RelativeLayout android:layout_width="fill_parent" android:layout_height="fill_parent"> <ImageView android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_gravity="center" android:adjustViewBounds="true" android:background="@android:color/holo_green_light" android:scaleType="centerCrop" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:gravity="center" android:text="第三屏:在慕課網跟著超哥好好學Android" android:textSize="18dp" android:textStyle="bold" /> </RelativeLayout> </ViewFlipper></RelativeLayout>可以看到其實 ViewFlipper 的子 View 和我們平時開發(fā)時用到的布局沒有什么差別,然后將每一屏的內容作為 ViewFlipper 的子 View 添加進去即可。

4.2 修改鏡像位置

由于 Maven 中央倉庫的服務器是架設在國外的,所以由于某種不可抗拒力量,國內用戶如果直接使用中央倉庫的話,下載速度會受很大的影響。如下圖所示,個人用戶可以使用阿里云鏡像。這里阿里云倉庫是作為中央倉庫的一個鏡像的,鏡像庫會完全屏蔽被鏡像庫。鏡像地址:<mirror> <id>alimaven</id> <name>aliyun maven</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <mirrorOf>central</mirrorOf></mirror>經過上面的簡單配置之后,我們就可以開心地使用 Maven 了。

2.4 運行這個項目

在項目創(chuàng)建好之后,我們就來運行這個項目。在 idea 中點擊Add Configuration按鈕。在彈出的界面點擊小+號,然后選擇 Tomcat Server -> Local。在 Tomcat Server 界面的 Deployment 頁簽,點擊小+號,選擇Artifact… ,并在彈出的窗口中,選擇mallweb:war exploded,并在Application context中輸入/index ,最后點擊OK按鈕。點擊Add Configuration 旁邊的三角形按鈕,啟動項目,項目啟動后控制臺會輸出如下日志產生。項目啟動成功后,我們在瀏覽器中輸入地址http://localhost:8080/index/,可以看到項目的歡迎頁面。至此,我們的項目已經創(chuàng)建并啟動完成。

1. Button 的基本用法

Button,顧名思義,就是一個提供給用戶點擊的控件。同 EditText 一樣,它也是繼承自 TextView,擁有 TextView 的全部屬性,這里重點講講 Button 特有的及常用的屬性的用法。下面我們通過 XML 定義了一個 Button:<Button xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/btn_click" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="點我" android:onClick="onClick" android:textColor="@android:color/holo_blue_dark" />基本用法和 TextView 類似,這里多了一個android:onClick屬性,從名字上看應該是設置一個點擊事件。那么我們首先看看 Button 最重要的功能,如何給 Button 綁定點擊事件接收器,從而完成 Button 點擊事件的接收及處理。

4.1 登錄頁面的編寫

登錄頁面就是主頁的 XML 布局文件,核心就是兩個輸入框,分別對應“賬號”和“密碼”,另外就是一個確認登錄的 Button。<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/account" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="100dp" android:layout_marginTop="150dp" android:text="賬號:" /> <EditText android:id="@+id/et_account" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="100dp" android:ems="10" /> <TextView android:id="@+id/password" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="100dp" android:text="密碼:" /> <EditText android:id="@+id/et_password" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="100dp" android:ems="10" android:inputType="textPassword" /> <Button android:id="@+id/login" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="100dp" android:text="登錄" /></LinearLayout>登錄界面如下:

3. 小結

總結 Nginx 部署 java 服務,主要是使用 proxy_pass 指令進行端口轉發(fā),因為都是 http 請求的轉發(fā),所以配置會比較簡單。但是如果服務較多時, Nginx 需要編寫多個 server 指令塊或者多個 location 塊去匹配不同的 URL并轉發(fā)到對應的上游服務。往往大型網站使用的服務較多時,會使用 include 指令對 Nginx 的配置進行拆分,不同的配置處理不同服務的轉發(fā),這樣會更簡潔明了,方便網站運維人員管理和修改 Nginx 配置。

2.1 get 無參數請求

我們直接使用 request 的 get 方法來請求慕課網,然后打印返回結果:import requestsr = requests.get('http://idcbgp.cn/')print(r.text)請求結果如下,格式為 HTML:<!DOCTYPE html><html><head><meta charset="utf-8"><title>慕課網-程序員的夢工廠</title><meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1"><meta name="renderer" content="webkit" /><meta name="mobile-agent" content="format=wml"; url="https://m.imooc.com/"><link rel="alternate" media="only screen and (max-width: 640px)" ><meta name="mobile-agent" content="format=xhtml"; url="https://m.imooc.com/"><meta name="mobile-agent" content="format=html5"; url="https://m.imooc.com/"><meta property="qc:admins" content="77103107776157736375" />......

1. 部署前端

前端框架如 Vue 打包出來往往是靜態(tài)的文件 index.html 加上一個 static 目錄。static 目錄下有 fonts、css、js、img等靜態(tài)資源目錄。前端的訪問是從 index.html 開始的。假設服務器上打包出的前端代碼放到/root/test-web目錄下,對應部署前端的配置如下:...http{ server { # 監(jiān)聽8080端口 listen 8080; # 指定域名,不指定也可以 server_name www.xxx.com; # 瀏覽器交互調參,打開gzip壓縮、緩存等等 gzip on; ... location / { root /root/test-web; # 也可以簡單使用 index index.html try_files $uri $uri/ /index.html; } # vue 頁面中向后臺 java 服務發(fā)送請求 ... }}...

1. TemplateView 類介紹和使用

TemplateView 視圖類是用來渲染給定的模板文件,其上下文字典包含從 URL 中捕獲的參數。首先來看看最簡單的模板渲染示例:準備模板文件,放到 template 目錄,在 settings.py 中需要配置好模板 (TEMPLATES) 相關的參數:[root@server first_django_app]# cat templates/test.html <p>{{ content }}</p><div>{{ spyinx.age }}</div>準備好類視圖,處理相關 HTTP 請求,這里使用今天要學習的 TemplateView 類:class TestTemplateView(TemplateView): template_name = 'test.html' @csrf_exempt def dispatch(self, request, *args, **kwargs): return super(TestTemplateView, self).dispatch(request, *args, **kwargs)配置相應的 URLConf:context_data = {'content':'正文1', 'spyinx':{'age': 29}}urlpatterns = [ path('test_template_view/', views.TestTemplateView.as_view(extra_context=context_data), name='test_template_View')]啟動 first_django_app 工程,然后使用 curl 命令簡單測試,發(fā)送 GET 請求:# 啟動first_django_app工程,監(jiān)聽8888端口(django-manual) [root@server first_django_app]# python manage.py runserver 0.0.0.0:8888Watching for file changes with StatReloaderPerforming system checks...System check identified no issues (0 silenced).April 16, 2020 - 12:27:49Django version 2.2.11, using settings 'first_django_app.settings'Starting development server at http://0.0.0.0:8888/Quit the server with CONTROL-C.# 打開xshell另一個窗口,使用curl發(fā)送get請求[root@server ~]# curl http://127.0.0.1:8888/hello/test_template_view/<p>正文1</p><div>29</div># 這個報405錯誤,不支持POST請求[root@server ~]# curl -XPOST http://127.0.0.1:8888/hello/test_template_view/[root@server ~]#可以看到,我們只是指定了 template_name 屬性,連對應的請求函數都沒寫 (寫的 dispatch() 函數只是為了能執(zhí)行POST請求,避免 csrf 報錯),只是繼承一下這個視圖類 (TemplateView) 能處理 GET 請求,并能返回渲染的模板,對一些情況來說是很方便的,節(jié)省了一些代碼。注意的是,其他請求方式不行,因為 TemplateView 內部只實現了 get() 方法,所以只能處理 GET 請求。上面的 context_data 是自定義的,現在我們來從數據庫中獲取數據,動態(tài)填充模板內容。同樣是涉及到模板視圖,所以可以通過 TemplateView 類來實現。首先準備新的模板文件 test.html。這個模板頁面使用表格分頁顯示會員數據,查詢的數據表是我們前面多次實驗的 member 表。<html><head><style type="text/css"> .page{ margin-top: 10px; font-size: 14px; } .member-table { width: 50%; text-align: center; }</style></head><body><p>會員信息-第{{ current_page }}頁, 每頁{{ page_size }}條, 總共{{ sum }}條</p><div><table border="1" class="member-table"> <thead> <tr> <th>姓名</th> <th>年齡</th> <th>性別</th> <th>職業(yè)</th> <th>所在城市</th> </tr> </thead> <tbody> {% for member in members %} <tr> <td>member.name</td> <td>member.age</td> {% if member.sex == 0 %} <td>男</td> {% else %} <td>女</td> {% endif %} <td>member.occupation</td> <td>member.city</td> </tr> {% endfor %} </tbody></table><div ><div class="page"></div></div></div></body></html>修改上面的視圖函數,分頁查詢 Member 表中的數據:class TestTemplateView(TemplateView): template_name = 'test.html' def get(self, request, *args, **kwargs): params = request.GET page = int(params.get('page', 1)) size = int(params.get('size', 5)) data = {} data['sum'] = Member.objects.all().count() members = Member.objects.all()[(page - 1) * size:page * size] data['current_page'] = page data['page_size'] = size data['members'] = members return self.render_to_response(context=data)這里我們使用了前面學習的 Django ORM 模型,獲取 member 表中的數據,然后使用 TemplateView 中的模板渲染方法 render_to_response() 返回渲染后的 HTML 文本給到客戶端。測試結果如下,以下兩張圖片是設置了不同的查詢參數(當前頁碼和頁大小),可以看到查詢參數是起了效果的:

3. 編寫路由

服務已經啟動了,接下來看看如何創(chuàng)建 gin 的路由。和前文一樣,我們先來創(chuàng)建一個處理 GET 請求的路由/index。代碼示例:package mainimport ( "net/http" "github.com/gin-gonic/gin")func main() { router := gin.Default() //創(chuàng)建get請求 router.GET("/index", func(c *gin.Context) { c.String(http.StatusOK, "<h1>Hello Codey!</h1>") }) router.Run("127.0.0.1:9300")}執(zhí)行上述代碼之后,在瀏覽器中輸入127.0.0.1:9300/index。你會發(fā)現**<h1>這個標簽沒有被瀏覽器識別,而是以字符串的形式輸出了**。這是因為 gin 框架中對輸出做出了嚴格的分類,在 c.String() 函數中輸出的值只會是字符串的形式輸出,這是在原生函數之上為了**防止XSS(****跨站腳本攻擊)**而做的優(yōu)化。所以它無法直接打印 html 腳本來渲染頁面,必須要使用 c.HTML() 函數來加載 html 文件。代碼示例:package mainimport ( "net/http" "github.com/gin-gonic/gin")func main() { router := gin.Default() router.LoadHTMLGlob("view/*")//設定html存放的文件目錄 router.GET("/index", func(c *gin.Context) { c.HTML(http.StatusOK, "index.html", nil)//打開view.index.html }) router.Run("127.0.0.1:9300")}目錄結構如下index.html 代碼如下:<!DOCTYPE html><html><head> <meta charset="utf-8"> <title>Go語言實戰(zhàn)2</title></head><body> <div> <h3>登錄</h3> <form action="check" method="POST"> <div> <div> <input type="text" id="username" name="username" placeholder="請輸入賬號"> </div> </div> <div> <div> <input type="password" class="form-control" id="password" name="password" placeholder="請輸入密碼"> </div> </div> <div > <div > <button id="loginbtn" type="submit" >登錄</button> </div> </div> </form> </div></body></html>執(zhí)行上述代碼之后,在瀏覽器中輸入127.0.0.1:9300/index。此處可以結合函數式編程的思想,將 index 的處理函數拿出來作為一個變量,代碼修改后如下所示:package mainimport ( "net/http" "github.com/gin-gonic/gin")func main() { router := gin.Default() router.LoadHTMLGlob("view/*") router.GET("/index", index) router.Run("127.0.0.1:9300")}func index(c *gin.Context) { c.HTML(http.StatusOK, "index.html", nil)}

4.3 登錄后的頁面

創(chuàng)建second.xml,作為登錄后頁面的布局文件:<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/result" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginTop="170dp" android:textSize="20sp" /> <Button android:id="@+id/logout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginTop="20dp" android:text="注銷登錄" /></LinearLayout>主要是一個歡迎頁面,帶上了用戶的賬號名,另外就是一個“注銷登錄”按鈕,可以刪除登錄記錄并跳轉回登錄首頁,登陸成功后頁面如下:

5.1 英雄登記頁面

等級頁面主要兩部分組成:**信息輸入框:**包含名稱、主打位置、特征確認登記 Button<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/fstTxt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="100dp" android:layout_marginTop="150dp" android:text="名稱" /> <EditText android:id="@+id/txtName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="100dp" android:ems="10" /> <TextView android:id="@+id/secTxt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="100dp" android:text="主打位置" /> <EditText android:id="@+id/txtLocation" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="100dp" android:ems="10" /> <TextView android:id="@+id/thirdTxt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="100dp" android:text="特征描述" /> <EditText android:id="@+id/txtDesignation" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="100dp" android:ems="10" /> <Button android:id="@+id/btnSave" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="100dp" android:text="確認登記" /></LinearLayout>

1.2 Nginx

Nginx 是異步框架的網頁服務器,也可以用作反向代理、負載平衡器和HTTP緩存:# 使用 Docker 啟動 redis 服務,端口默認,使用host網絡模式保障性能。# 使用自己的html目錄,ro設定宿主機目錄掛載到容器后,容器對此目錄只讀docker run --restart=always --network host -d -it -v ~/docker/nginx/html:/usr/share/nginx/html:ro --name mynginx nginx 指定自己的配置文件 # 先將配置文件放到 ~/docker/nginx/etc/nginx 目錄下 docker run --restart=always --network host -d -it -v ~/docker/nginx/html:/usr/share/nginx/html:ro -v ~/docker/nginx/etc/nginx:/etc/nginx --name mynginx nginx 查看更多

2.2 服務提供者測試

首先我們需要啟動 Zookeeper 服務,然在啟動服務提供者主類的 main 方法package cn.cdd.zookeeper.provider;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class ZookeeperProviderApplication { public static void main(String[] args) { SpringApplication.run(ZookeeperProviderApplication.class, args); }}啟動完成后,控制臺輸出:>>> 服務提供者連接 Zookeeper ...>>> 本服務已上線看到服務上線的消息,我們就可以使用瀏覽器訪問 http://localhost:8090/provider/callMethod ,觀察瀏覽器調用了服務提供者 192.168.0.102:8090 的方法我們可以看到瀏覽器顯示的信息,說明服務提供者測試成功。接下來我們就可以開始構建服務消費者項目了。

8. 運行測試

終端中運行當前程序(與運行 Django 一樣):python manage.py runserver此時,我們的 RESTful Web API 已構建完畢。由于我們是在本地測試,所以 API 域名部分采用本機地址。在瀏覽器地址欄輸入 http://127.0.0.1:8000/api,即可看到當前項目中所有接口連接。點擊鏈接 http://127.0.0.1:8000/api/students/ 即可前往學生信息接口,可以獲取所有學生的信息,如下圖所示:在頁面底部的表單中,我們可以輸入學生信息,點擊 POST 按鈕,即可實現向學生列表中添加新的學生信息:點擊 POST 按鈕后,返回如下信息:此時再點擊 GET 按鈕,我們發(fā)現上一步中添加的學生(小白)已經顯示在所有學生信息中。在瀏覽器中輸入網址 127.0.0.1:8000/api/students/2/,可以訪問獲取單個學生信息的接口(id 為 2 的學生),如下圖所示:如果需要修改該學生的信息,可在頁面底部表單中填寫需要修改的信息,即可訪問修改單個學生的接口。我們將小紅年齡修改為 20:點擊 PUT,返回如下頁面信息,此時小紅的年齡信息已經修改完畢:點擊 DELETE 按鈕,可以訪問刪除學生的接口:點 DELETE 后返回,如下頁面,此時 id 為 2 的學生小紅已被刪除:

3.1 ArrayAdapter 的用法

ArrayAdpater 的用法非常簡單,如上一小節(jié)所說,它適合列表是單項列表并且數據可以存在一個數據當中的場景。首先我們創(chuàng)建布局文件,里面只需要存放一個 ListView 控件即可:<ListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/simpleListView" android:layout_width="match_parent" android:layout_height="wrap_content" android:divider="#000" android:dividerHeight="2dp" />其中有兩個屬性大家可能比較陌生: android:divider="#000" android:dividerHeight="2dp"這兩個屬性是用來設置列表項之間的分割線樣式的,詳細的會在 ListView 章節(jié)進行介紹。然后還需要編寫列表中每個列表項的布局樣式,我們只需要一個 TextView 來顯示文本,而文本的內容就是數組的數據,列表項布局代碼 list_view.xml 如下:<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/textView" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:padding="30dp" android:textColor="#000" /></LinearLayout>一個我們非常熟悉的 TextView,然后就可以在 Java 代碼中通過 ArrayAdapter進行數據 / UI 的綁定了,Java 代碼如下:package com.emercy.myapplication;import android.os.Bundle;import android.widget.ArrayAdapter;import android.widget.ListView;import android.app.Activity;public class MainActivity extends Activity { ListView mList; String mNums[] = {"TextView", "EditText", "Button", "ImageButton", "RadioButton", "ToggleButton", "ImageView", "ProgressBar", "SeekBar", "RatingBar", "ScrollView", "Adapter"}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mList = findViewById(R.id.simpleListView); ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(this, R.layout.list_view, R.id.textView, mNums); mList.setAdapter(arrayAdapter); }}我們在 OnCreate() 中獲取ListView對象,然后創(chuàng)建 ArrayAdapter,傳入列表項的布局文件 ID、需要顯示內容的 TextView 控件 ID 以及數組形式的數據。最后通過 setAdapter 完成數據及 UI 的綁定,這樣系統(tǒng)就會幫我們完成適配工作,效果如下:我們寫在數組中的數據就會按順序填充到列表中了。

3.2 普通對話框

一個普通的 AlertDialog 也是大家日常見到最多的一種,直接彈出一個提示,然后給出 1 - 3 個選項,比如“是”、“否”、“取消”等等,使用方法非常簡單,基本上可以直接套用 3.1小節(jié)的步驟,代碼如下:package com.emercy.myapplication;import android.app.Activity;import android.app.AlertDialog;import android.content.DialogInterface;import android.os.Bundle;import android.view.View;import android.view.View.OnClickListener;import android.widget.Toast;public class MainActivity extends Activity implements OnClickListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.alert).setOnClickListener(this); } @Override public void onClick(View v) { AlertDialog.Builder builder = new AlertDialog.Builder(this); AlertDialog alertDialog = builder .setIcon(R.drawable.warning) .setTitle("系統(tǒng)消息:") .setMessage("彈出一個普通的AlertDialog,\n提供確定、退出、取消三個Button") .setPositiveButton("確定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Toast.makeText(MainActivity.this, "已確定", Toast.LENGTH_SHORT).show(); } }) .setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Toast.makeText(MainActivity.this, "已為您取消", Toast.LENGTH_SHORT).show(); } }).setNeutralButton("退出", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Toast.makeText(MainActivity.this, "已退出對話框", Toast.LENGTH_SHORT).show(); } }).create(); // 通過 create() 創(chuàng)建AlertDialog對象 alertDialog.show(); // 通過 show() 展示對話框 }}然后為 Activity 編寫一個布局文件,其中放置一個 Button 用于觸發(fā)對話框,如下:<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="MainActivity"> <Button android:layout_centerInParent="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="彈出普通 AlertDialog" android:id="@+id/alert" /></RelativeLayout>編譯之后點擊屏幕中間的 Button,彈出來的就是一個普通對話框了。我們設置了“確定”、“取消”、“中立” 3 個Button,分別表示“確定”、“取消”以及“退出對話框” 3 種操作,實際使用中,可以在回調接口里針對 3 種 Button 設置不同的回調邏輯,效果如下:

4.1 布局文件

布局文件基本一樣,只是默認將進度條和進度顯示隱藏起來,等到onPreExecute()方法的時候在做初始化展示,代碼如下:<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ProgressBar android:visibility="gone" android:id="@+id/progressBar" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentStart="true" android:layout_alignParentTop="true" /> <Button android:id="@+id/start_progress" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/progressBar" android:layout_alignParentStart="true" android:layout_marginStart="24dp" android:layout_marginTop="62dp" android:text="開始任務" /> <TextView android:visibility="gone" android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignTop="@+id/start_progress" android:layout_alignBottom="@+id/start_progress" android:layout_alignParentEnd="true" android:layout_marginEnd="85dp" android:gravity="center" android:text="當前進度:0%" android:textSize="16sp" /></RelativeLayout>

5. 小結

本節(jié)我們討論了 CSRF 的含義及在 Spring Security 中配置方法:CSRF 是一種常見的 B/S 攻擊形式;CSRF 可以在瀏覽器上偽造用戶的請求;CSRF 的防御思路是確保發(fā)送請求的來源是可信的;CSRF 的防御方法包含「同步 Token」和「設置 SameSite 參數」兩種,其中「SameSite」參數方式需要瀏覽器的支持;Spring Security 默認已開啟 CSRF 保護。下節(jié)我們討論 Spring Security 對 HTTP 請求常用的和安全相關的頭部信息。

2. 深入理解 Django 類視圖

這里在介紹完類視圖的基本使用后,我們來深入學習下 Django 的源代碼,看看 Django 是如何將對應的 HTTP 請求映射到對應的函數上。這里我們使用的是 Django 2.2.10 的源代碼進行說明。我們使用 VSCode 打開 Django 源碼,定位到 django/views/generic 目錄下,這里是和視圖相關的源代碼。首先看 __init__.py 文件,內容非常少,主要是將該目錄下的常用視圖類導入到這里,簡化開發(fā)者導入這些常用的類。其中最重要的當屬 base.py 文件中定義的 view 類,它是其他所有視圖類的基類。# base.py中常用的三個view類from django.views.generic.base import RedirectView, TemplateView, View# dates.py中定義了許多和時間相關的視圖類from django.views.generic.dates import ( ArchiveIndexView, DateDetailView, DayArchiveView, MonthArchiveView, TodayArchiveView, WeekArchiveView, YearArchiveView,)# 導入DetailView類from django.views.generic.detail import DetailView# 導入增刪改相關的視圖類from django.views.generic.edit import ( CreateView, DeleteView, FormView, UpdateView,)# 導入list.py中定義的顯示列表的視圖類from django.views.generic.list import ListView__all__ = [ 'View', 'TemplateView', 'RedirectView', 'ArchiveIndexView', 'YearArchiveView', 'MonthArchiveView', 'WeekArchiveView', 'DayArchiveView', 'TodayArchiveView', 'DateDetailView', 'DetailView', 'FormView', 'CreateView', 'UpdateView', 'DeleteView', 'ListView', 'GenericViewError',]# 定義一個通用的視圖異常類class GenericViewError(Exception): """A problem in a generic view.""" pass接下來,我們查看 base.py 文件,重點分析模塊中定義的 View 類:# 源碼路徑 django/views/generic/base.py# 忽略導入# ...class View: http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] def __init__(self, **kwargs): # 忽略 # ... @classonlymethod def as_view(cls, **initkwargs): """Main entry point for a request-response process.""" for key in initkwargs: if key in cls.http_method_names: raise TypeError("You tried to pass in the %s method name as a " "keyword argument to %s(). Don't do that." % (key, cls.__name__)) if not hasattr(cls, key): raise TypeError("%s() received an invalid keyword %r. as_view " "only accepts arguments that are already " "attributes of the class." % (cls.__name__, key)) def view(request, *args, **kwargs): self = cls(**initkwargs) if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get self.setup(request, *args, **kwargs) if not hasattr(self, 'request'): raise AttributeError( "%s instance has no 'request' attribute. Did you override " "setup() and forget to call super()?" % cls.__name__ ) return self.dispatch(request, *args, **kwargs) view.view_class = cls view.view_initkwargs = initkwargs # take name and docstring from class update_wrapper(view, cls, updated=()) # and possible attributes set by decorators # like csrf_exempt from dispatch update_wrapper(view, cls.dispatch, assigned=()) return view # ... def dispatch(self, request, *args, **kwargs): # Try to dispatch to the right method; if a method doesn't exist, # defer to the error handler. Also defer to the error handler if the # request method isn't on the approved list. if request.method.lower() in self.http_method_names: handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed return handler(request, *args, **kwargs) def http_method_not_allowed(self, request, *args, **kwargs): logger.warning( 'Method Not Allowed (%s): %s', request.method, request.path, extra={'status_code': 405, 'request': request} ) return HttpResponseNotAllowed(self._allowed_methods()) # 忽略其他函數 # ...# ...我們來仔細分析 view 類中的這部分代碼。view 類首先定義了一個屬性 http_method_names,表示其支持的 HTTP 請求方法。接下來最重要的是 as_view() 方法和 dispatch() 方法。在上面使用視圖類的示例中,我們定義的 URLConf 如下:# first_django_app/hello_app/urls.pyfrom . import viewsurlpatterns = [ # 類視圖 url(r'test-cbv/', views.TestView.as_view(), name='test-cbv'),]這里結合源碼可以看到,views.TestView.as_view() 返回的結果同樣是一個函數:view(),它的定義和前面的視圖函數一樣。as_view() 函數可以接收一些參數,函數調用會先對接收的參數進行檢查:for key in initkwargs: if key in cls.http_method_names: raise TypeError("You tried to pass in the %s method name as a " "keyword argument to %s(). Don't do that." % (key, cls.__name__)) if not hasattr(cls, key): raise TypeError("%s() received an invalid keyword %r. as_view " "only accepts arguments that are already " "attributes of the class." % (cls.__name__, key))上面的代碼會對 as_view() 函數傳遞的參數做兩方面檢查:首先確保傳入的參數不能有 get、post 這樣的 key 值,否則會覆蓋 view 類中的對應方法,這樣對應的請求就無法正確找到函數進行處理。覆蓋的代碼邏輯如下:class View: # ... def __init__(self, **kwargs): # 這里會將所有的傳入的參數通過setattr()方法給屬性類賦值 for key, value in kwargs.items(): setattr(self, key, value) # ... @classonlymethod def as_view(cls, **initkwargs): # ... def view(request, *args, **kwargs): # 調用視圖函數時,會將這些參數傳給View類來實例化 self = cls(**initkwargs) # ... # ... # ...此外,不可以傳遞類中不存在的屬性值。假設我們將上面的 URLConf 進行略微修改,如下:from . import viewsurlpatterns = [ # 類視圖 url(r'test-cbv/', views.TestView.as_view(no_key='hello'), name='test-cbv'),]啟動后,可以發(fā)現 Django 報錯如下,這正是由本處代碼拋出的異常。接下來看下 update_wrapper() 方法,這個只是 python 內置模塊中的一個方法,只是比較少用,所以會讓很多人感到陌生。先看它的作用:update_wrapper() 這個函數的主要功能是負責復制原函數的一些屬性,如 moudle、name、doc 等。如果不加 update_wrapper(), 那么被裝飾器修飾的函數就會丟失其上面的一些屬性信息。具體看一個測試代碼示例:from functools import update_wrapperdef test_wrapper(f): def wrapper_function(*args, **kwargs): """裝飾函數,不保留原信息""" return f(*args, **kwargs) return wrapper_functiondef test_update_wrapper(f): def wrapper_function(*args, **kwargs): """裝飾函數,使用update_wrapper()方法保留原信息""" return f(*args, **kwargs) update_wrapper(wrapper_function, f) return wrapper_function@test_wrapperdef test_wrapped(): """被裝飾的函數""" pass@test_update_wrapperdef test_update_wrapped(): """被裝飾的函數,使用了update_wrapper()方法""" passprint('不使用update_wrapper()方法:')print(test_wrapped.__doc__) print(test_wrapped.__name__) print()print('使用update_wrapper()方法:')print(test_update_wrapped.__doc__) print(test_update_wrapped.__name__) 執(zhí)行結果如下:不使用update_wrapper()方法:裝飾函數,不保留原信息wrapper_function使用update_wrapper()方法:被裝飾的函數,使用了update_wrapper()方法test_update_wrapped可以看到,不使用 update_wrapper() 方法的話,函數在使用裝飾器后,它的一些基本屬性比如 __name__ 等都是正真執(zhí)行函數(比如上面的 wrapper_function() 函數)的屬性。不過這個函數在分析視圖函數的處理流程上并不重要。接下來看 as_view 中定義的 view() 方法,它是真正執(zhí)行 HTTP 請求的視圖函數:def view(request, *args, **kwargs): self = cls(**initkwargs) # 如果有get方法而沒有head方法,對于head請求則直接使用get()方法進行處理 if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get # 將Django對應傳過來的請求實例以及相應參數賦給實例屬性 self.setup(request, *args, **kwargs) # 如果沒有request屬性,表明可能重寫了setup()方法,而且setup()里面忘記了調用super() if not hasattr(self, 'request'): raise AttributeError( "%s instance has no 'request' attribute. Did you override " "setup() and forget to call super()?" % cls.__name__ ) # 調用dispatch()方法 return self.dispatch(request, *args, **kwargs)view() 方法里面會調用 setup() 方法將 Django 給視圖函數傳遞的參數賦給實例變量,然后會調用 dispatch()方法去處理請求。兩個函數的代碼如下:def setup(self, request, *args, **kwargs): """Initialize attributes shared by all view methods.""" self.request = request self.args = args self.kwargs = kwargsdef dispatch(self, request, *args, **kwargs): # Try to dispatch to the right method; if a method doesn't exist, # defer to the error handler. Also defer to the error handler if the # request method isn't on the approved list. if request.method.lower() in self.http_method_names: handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed return handler(request, *args, **kwargs)這里最核心的就是這個 dispatch() 方法了。首先該方法通過 request.method.lower() 這個可以拿到 http 的請求方式,比如 get、post、put 等,然后判斷是不是在預先定義好的請求方式的列表中。如果滿足,那么最核心的代碼來了:handler = getattr(self, request.method.lower(), self.http_method_not_allowed)假設客戶端發(fā)的是 get 請求,那么 request.method.lower() 就是 “get” ,接下來執(zhí)行上面的代碼,就會得到我們定義的視圖類中定義的 get 函數,最后返回的是這個函數的處理結果。這就是為啥 get 請求能對應到視圖函數中get() 方法的原因。其他的請求也是類似的,如果是不支持的請求,則會執(zhí)行 http_method_not_allowed() 方法。return handler(request, *args, **kwargs)如果對這部分代碼的執(zhí)行流程還有疑問的,我們可以在 Django 的源碼中添加幾個 print() 函數,然后通過實際請求來看看執(zhí)行過程:[root@server first_django_app]# cat ~/.pyenv/versions/django-manual/lib/python3.8/site-packages/django/views/generic/base.py class View: ... @classonlymethod def as_view(cls, **initkwargs): ... def view(request, *args, **kwargs): print('調用view函數處理請求') ... ... def dispatch(self, request, *args, **kwargs): # Try to dispatch to the right method; if a method doesn't exist, # defer to the error handler. Also defer to the error handler if the # request method isn't on the approved list. print('調用dispatch()方法處理http請求,請求方式:{}'.format(request.method.lower())) if request.method.lower() in self.http_method_names: handler = getattr(self, request.method.lower(), self.http_method_not_allowed) print('得到的handler:{}'.format(handler)) else: handler = self.http_method_not_allowed return handler(request, *args, **kwargs)接下來我們還是使用前面定義的視圖類 TestView 來進行操作,操作過程以及實驗結果如下:# 一個窗口啟動 django 工程(django-manual) [root@server first_django_app]# python manage.py runserver 0.0.0.0:8888Watching for file changes with StatReloaderPerforming system checks...System check identified no issues (0 silenced).April 15, 2020 - 04:30:04Django version 2.2.11, using settings 'first_django_app.settings'Starting development server at http://0.0.0.0:8888/Quit the server with CONTROL-C.# 另一個窗口發(fā)送http請求[root@server django-manual]# curl -XGET http://127.0.0.1:8888/hello/test-cbv/hello, get[root@server django-manual]# curl -XPOST http://127.0.0.1:8888/hello/test-cbv/hello, post[root@server django-manual]# curl -XPUT http://127.0.0.1:8888/hello/test-cbv/hello, put[root@server django-manual]# curl -XDELETE http://127.0.0.1:8888/hello/test-cbv/hello, delete

3.2 PyDev + Eclipse

Eclipse 是 Java 語言的 IDE,PyDev 是其一個插件,安裝后,可以在 Eclispe 中進行 Python 的開發(fā)工作。優(yōu)點:提供了代碼語法高亮顯示、解析器錯誤、代碼折疊和多語言支持;具有良好的界面視圖,提供一個交互式控制臺;支持 CPython、Jython、Iron Python 和 Django,并允許在掛起模式下進行交互式測試。缺點:如果應用程序太大,使用多個插件,PyDev IDE 的性能會降低;作為插件,在實際使用過程中不是很穩(wěn)定。 圖片來源:http://www.pydev.org 官網

3.1 編寫主頁面布局

整體的頁面布局對按照第 2 小節(jié)所講的規(guī)則來編寫,包含幾個元素:主頁面標題、主頁面內容、側滑菜單。布局代碼如下:<?xml version="1.0" encoding="utf-8"?><androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:text="@string/drawer_prompt" android:textSize="30sp" /> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="50dp" android:gravity="center" android:text="側滑菜單" /> <ImageView android:id="@+id/imageview" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout> <ListView android:id="@+id/left_drawer" android:layout_width="240dp" android:layout_height="match_parent" android:layout_gravity="start" android:background="#FFFFFF" android:choiceMode="singleChoice" android:divider="@android:color/darker_gray" android:dividerHeight="1dp" /></androidx.drawerlayout.widget.DrawerLayout>

4. Toast 的使用示例

本節(jié)我們用 Toast 實現一個“農藥”提示,有兩個 Button,點擊會觸發(fā)一個短暫的消息提示。代碼比較簡單,首先在布局中加入兩個 Button:<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:padding="10dp" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/dont_wave" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="猥瑣發(fā)育,別浪" /> <Button android:id="@+id/hold_on_we_can_win" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="穩(wěn)住,我們能贏" /></LinearLayout>接著在 Java 代碼中注冊監(jiān)聽器,在點擊不同 Button 的時候彈出不同的 Toast 提示信息:package com.emercy.myapplication;import android.app.Activity;import android.os.Bundle;import android.view.Gravity;import android.view.LayoutInflater;import android.view.View;import android.widget.LinearLayout;import android.widget.TextView;import android.widget.Toast;public class MainActivity extends Activity implements View.OnClickListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.dont_wave).setOnClickListener(this); findViewById(R.id.hold_on_we_can_win).setOnClickListener(this); } @Override public void onClick(View v) { showToast(((TextView) v).getText().toString()); } private void showToast(String text) { LinearLayout layout = (LinearLayout) LayoutInflater.from(this).inflate(R.layout.toast, null); TextView textView = layout.findViewById(R.id.text); textView.setText(text); Toast toast = new Toast(this); toast.setView(layout); toast.setDuration(Toast.LENGTH_SHORT); toast.setGravity(Gravity.TOP, 0, 500); toast.show(); }}可以看到在創(chuàng)建 Toast 之后,通過setView方法設置了一個 LinearLayout 類型的 View 對象。通過這種方式就可以自定義一個展示樣式,最后編寫 Toast 的布局樣式代碼:<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:gravity="bottom" android:layout_height="match_parent" android:orientation="horizontal"> <ImageView android:layout_width="50dp" android:layout_height="50dp" android:src="@mipmap/mc" /> <TextView android:id="@+id/text" android:layout_marginLeft="10dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="30sp" /></LinearLayout>XML 文件中我們橫向放置了一個圖片和一個文本,對應的是游戲人物和提示語,然后在點擊的時候彈出此樣式的 Toast。最終編譯運行效果如下:

2. 發(fā)送一個請求

構建了 xhr 對象之后,我們可以通過方法的調用來進行請求的發(fā)送。xhr.open('GET', 'http://www.example.com');xhr.send();這是最簡單最典型的發(fā)送請求的做法。只需要短短 2 行代碼,我們就可以執(zhí)行一個請求發(fā)送動作。實際上 XMLHttpRequest.open 這個方法的參數不止兩個這么少,一共有 5 個參數:xhrReq.open(method, url, async, user, password);這些參數分別代表著:method: 代表HTTP請求的方法名,比如 GET、POST、 PUT 和 DELETE。url: 一個DomString,代表著要想向其發(fā)送請求的 url。async: 表示是否異步。user:用戶名,用于認證用途。password:密碼,用于認證用途。其中,user 和 password 都是用于認證用途。而前 3 個參數是我們經常都會使用到的。這里著重說的是參數 async。默認情況下,async 為 true,代表著請求將是異步的。當然我們也可以設置為 false,這樣我們就可以同步請求了。然而,事實上我們應該盡量不這么做,因為同步的請求會阻塞我們的UI和一切用戶活動,造成的體驗非常不好。到目前為止,如果你也跟著做的話應該能看到已經可以發(fā)送一個 Ajax 請求了,雖然它是失敗的,因為你并沒有正確的服務能夠處理這個請求。如果你在瀏覽器上運行,打開控制臺,你應該會得到這樣的一個效果:

直播
查看課程詳情
微信客服

購課補貼
聯系客服咨詢優(yōu)惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號