久久精品国产精品国产精品污,男人扒开添女人下部免费视频,一级国产69式性姿势免费视频,夜鲁夜鲁很鲁在线视频 视频,欧美丰满少妇一区二区三区,国产偷国产偷亚洲高清人乐享,中文 在线 日韩 亚洲 欧美,熟妇人妻无乱码中文字幕真矢织江,一区二区三区人妻制服国产

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > HTML >内容正文

HTML

14W 行代码量的前端页面长什么样

發布時間:2024/2/28 HTML 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 14W 行代码量的前端页面长什么样 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

作者:sigmaliu,騰訊文檔 AlloyTeam 開發工程師

0. 前言

騰訊文檔列表頁在不久前經歷了一次完全重構后,首屏速度其實已經是不錯。但是我們仍然可以引入 SSR 來進一步加快速度。這篇文章就是用來記錄和整理我最近實現 SSR 遇到的一些問題和思考。雖然其中有一些基礎設施可能和騰訊或文檔強相關,但是作為一篇涉及 Node、React 組件、性能、網絡、docker 鏡像 、云上部署、灰度和發布等內容的文章,仍然可以小小地作為參考或者相似需求的 Checklist。

就是這樣一個頁面,內部邏輯復雜,優秀的重構同學做到了組件盡可能地復用,未壓縮的編譯后開發代碼仍然有 14W 行,因此也不算標題黨了。

1. 整體流程

1.1 CSR

我們回顧 CSR(客戶端渲染)的流程

  • 一個 React 應用,通常我們把 CSS 放在 head,有個 React 應用掛載的根節點空標簽,以及 React 應用編譯后的主體文件。瀏覽器在加載 HTML 后,加載 CSS 和 JS,到這時候為止,瀏覽器呈現給用戶的仍然是個空白的頁面。

  • <紅色箭頭部分> JS 開始執行,狀態管理會初始化個 store,會先拿這個 store 去渲染頁面,這時候頁面開始渲染元素(白屏時間結束)。但是還沒有列表的詳細信息,也沒有頭像、用戶名那些信息。

  • 初始化 store 后會發起異步的 CGI 請求,在請求回來后會更新 store,觸發 React 重新渲染頁面,綁定事件,整個頁面完全呈現(首屏時間結束)。

  • 1.2 SSR

  • <綠色箭頭部分> 首先我們復用原來的 React 組件編譯出可以在 Node 環境下運行的文件,并且部署一個 Node 服務。

  • <藍色箭頭部分> 在瀏覽器發起 HTML 請求時,我們的 Node 服務會接收到請求。可以從請求里取出 HTTP 頭部,Cookie 等信息。運行對應的 JS 文件,初始化 store,發起 CGI 請求填充數據,調用 React 渲染 DOM 節點(這里和 CSR 的差異在于我們得等 CGI 請求回來數據改變后再渲染,也就是需要的數據都準備好了再渲染)。

  • 將渲染的 DOM 節點插入到原 React 應用根節點的內部,同時將 store 以全局變量的形式注入到文檔里,返回最終的頁面給瀏覽器。瀏覽器在拿到頁面后,加上原來的 CSS,在 JS 下載下來之前,就已經能夠渲染出完整的頁面了(白屏時間結束、首屏時間結束)。

  • <紅色箭頭部分> JS 開始執行,拿服務端注入的數據初始化 store,渲染頁面,綁定事件(可交互時間結束)(這里其實后面可能還有一些 CGI,因為有一些 CGI 是不適合放在服務端的,且不影響首頁直出的頁面,會放在客戶端上加快首屏速度。這里的一個優化點在于我們將盡量避免在服務端有串行的 CGI 存在,比如需要先發起一個 CGI,等結果返回后才發起另外一個 CGI,因為這會將 SSR 完全拖垮一個 CGI 的速度)。

  • 2. 入口文件

    2.1 服務端入口文件

    要把代碼在 Node 下跑起來,首先要編譯出文件來。除了原來的 CSR 代碼外,我們創建一個 Node 端的入口文件,引入 CSR 的 React 組件。

    (async () => {const store = useStore();await Promise.all([store.dispatch.user.requestGetUserInfo(),store.dispatch.list.refreshRecentOpenList(),]);const initialState = store.getState();const initPropsDataHtml = getStateScriptTag(initialState);const bodyHtml = ReactDOMServer.renderToString(<Provider store={store}><ServerIndex /></Provider>);// 回調函數,將結果返回的TSRCALL.tsrRenderCallback(false, bodyHtml + initPropsDataHtml); })();

    服務端的 store,Provider, reducer,ServerIndex 等都是復用的客戶端的,這里的結構和以下客戶端渲染的一致,只不過多了 renderToString 以及將結果返回的兩部分。

    2.2 客戶端入口文件

    相應的,客戶端的入口文件做一點改動:

    export default function App() {const initialState = window.__initial_state__ || undefined;const store = useStore(initialState);// 額外判斷數據是否完整的const { getUserInfo, recentList } = isNeedToDispatchCGI(store);useEffect(() => {Promise.race([getUserInfo && store.dispatch.user.requestGetUserInfo(),store.dispatch.notification.requestGetNotifyNum(),]).finally(async () => {store.dispatch.banner.requestGetUserGrowthBanner();recentList && store.dispatch.list.requestRecentOpenList();});}, []); }

    主要是復用服務端注入到全局變量的數據以及 CGI 是否需要重發的判斷。

    2.3 代碼編譯

    將服務端的代碼編譯成 Node 下運行的文件,最主要的就是設置 webpack 的 target: 'node' ,以及為了在復用的代碼里區分服務端還是客戶端,會注入編譯變量。

    new?webpack.DefinePlugin({__SERVER__:?(process.env.RENDER_ENV?===?'server'), })

    其他的大部分保持和客戶端的編譯配置一樣就 OK 了,一些細微的調整后面會說到。

    3. 代碼改造

    將代碼編譯出來,但是先不管跑起來能否結果一致,能不報錯大致跑出個 DOM 節點來又是另外一回事。

    3.1 運行時差異

    首先擺在我們前面的問題在于瀏覽器端和 Node 端運行環境的差異。就最基本的,window,document 在 Node 端是沒有的,相應的,它們以下的好多方法就不能使用。我們當然可以選擇使用 jsdom 來模擬瀏覽器環境,以下是一個 demo:

    const?jsdom?=?require("jsdom"); const?{?JSDOM?}?=?jsdom; const?{?window?}?=?new?JSDOM(``,?{url:?'http://localhost', });global.localStorage?=?window.localStorage;localStorage.setItem('AlloyTeam',?'NB'); console.log(localStorage.getItem('AlloyTeam'));//?NB

    但是當我使用的時候,有遇到不支持的 API,就需要去補 API。且在 Node 端跑預期之外的代碼,生成的是否是預期的結果也是存疑,工作量也會較大,因此我選擇用編譯變量來屏蔽不支持的代碼,以及在全局環境下注入很有限的變量(vm + context)。

    3.2 非必需依賴

    對于不支持 Node 環境的依賴模塊來說,比如瀏覽器端的上報庫,統一的打開窗口的庫,模塊動態加載庫等,對首頁直出是不需要的,可以選擇配置 alias 并使用空函數代替防止調用報錯或 ts 檢查報錯。

    alias:?{src:?path.resolve(projectDir,?'src'),'@tencent/tencent-doc-report':?getRewriteModule('./tencent-doc-report.ts'),'@tencent/tencent_doc_open_url':?getRewriteModule('./tencent-doc-open-url.ts'),'script-loader':?getRewriteModule('./script-loader.ts'),'@tencent/docs-scenario-components-message-center':?getRewriteModule('./message-center.ts'),'@tencent/toggle-client-js':?getRewriteModule('./tencent-client-js.ts'), },

    例如里面的 script-loader(模塊加載器,用來動態創建 <script> 標簽注入 JS 模塊的),整個模塊屏蔽掉。

    const?anyFunc?=?(...args:?any[])?=>?{};export?const?ScriptLoader?=?{init:?anyFunc,load:?anyFunc,listen:?anyFunc,dispatch:?anyFunc,loadRemote:?anyFunc,loadModule:?anyFunc, };

    3.3 必需依賴

    對于必需的依賴但是又不支持 Node 環境的,也只能是推動兼容一下。整個過程來說只有遇到兩個內部模塊是不支持的,兼容工作很小。對于社區成熟的庫,很多都是支持 Node 下環境的。

    比如組件庫里默認的掛載點,在默認導出里使用 document.body ,只要多一下判斷就可以了。

    3.4 不支持的方法

    舉一些不支持方法的案例:

    像這種在組件渲染完成后注冊可見性事件的,明顯在服務端是不需要的,直接屏蔽就可以了。

    export?const?registerOnceVisibilityChange?=?()?=>?{if?(__SERVER__)?{return;}if?(onVisibilityChange)?{removeVisibilityChange(onVisibilityChange);} };

    useLayoutEffect 在服務端不支持,也應該屏蔽。但是需要看一下是否需要會有影響的邏輯。比如有個組件是 Slide,它的功能就像是頁簽,在子組件掛載后,切換子組件的顯示。在服務端上明顯是沒有 DOM 掛載后的回調的,因此在服務端就需要改成直接渲染要顯示的子組件就可以了。

    export?default?function?TransitionView({?visible?=?false,?...props?}:?TransitionViewProps)?{if?(!__SERVER__)?{useLayoutEffect(()?=>?{},?[visible,?props.duration]);useLayoutEffect(()?=>?{},?[_visible]);} }

    useMemo 方法在服務端也不支持。

    export?function?useStore(initialState?:?RootStore)?{if?(__SERVER__)?{return?initializeStore(initialState);}return?useMemo(()?=>?initializeStore(initialState),?[initialState]); }

    總的來說使用屏蔽的方法,加上注入的有限的全局變量,其實屏蔽的邏輯不多。對于引入 jsdom 來說,結果可控,工作量又小。

    3.5 基礎組件庫 DUI

    對于要直出一個 React 應用,基礎組件庫的支持是至關重要的。騰訊文檔里使用自己開發的 DUI 組件庫,因為之前沒有 SSR 的需求,所以雖然代碼里有一些支持 Node 環境的邏輯,但是還不完善。

    3.5.1 后渲染組件

    有一些組件需要在鼠標動作或者函數式調用才渲染的,比如 Tooltip,Dropdown,Menu,Modal組件等。在特定動作后才渲染子組件。在服務端上,并不會觸發這些動作,就可以用空組件代替。(理想情況當然是組件里原生支持 Node 環境,但是有五六個組件需要支持,就先在業務里去兼容,也算給組件庫提供個思路)

    以 Tooltip 為例,這樣可以支持組件同時運行在服務端和客戶端,這里還補充了 className,是因為發現這個組件的根節點設置的樣式會影響子組件的顯示,因此加上。

    import { Tooltip as BrowserTooltip } from '@tencent/dui/lib/components/Tooltip'; import { ITooltipProps } from './interface';function ServerTooltip(props: ITooltipProps) {// 目前知道這個 tooltip 的樣式會影響,因此加上 dui 的樣式return (<div className="dui-trigger dui-tooltip dui-tooltip-wrapper">{props.children}</div>); }const Tooltip = __SERVER__ ? ServerTooltip : BrowserTooltip;export default Tooltip;

    3.5.2 動態插入樣式

    DUI 組件會在第一次運行的時候會將對應組件的樣式使用 <style> 標簽動態插入。但是當我們在服務端渲染,是沒有節點讓它插入樣式的。因此是在 vm 里提供了一些全局方法,供運行代碼可以在文檔的指定位置插入內容。需要注意的是我們首屏可能只用到了幾個組件,但是如果把所有的組件樣式都插到文檔里,文檔將會變大不少,因此還需要過濾一下。

    if?(isBrowser)?{const?styleElement?=?document.createElement('style');styleElement.setAttribute('type',?'text/css');styleElement.setAttribute('data-dui-key',?key);styleElement.innerText?=?css;document.head.appendChild(styleElement); }?else?if?(typeof?injectContentBeforeRoot?===?'function')?{const?styleElement?=?`<style?type="text/css"?data-dui-key="${key}">${css}</style>`;injectContentBeforeRoot(styleElement); }

    同時組件用來在全局環境下管理版本號的方法,也需要抹平瀏覽器端和 Node 端的差異(這里其實還可以實現將 window.__dui_style_registry__ 注入到文檔里,客戶端從全局變量取出,實現復用)。

    class?StyleRegistryManage?{nodeRegistry:?Record<string,?string[]>?=?{};constructor()?{if?(isBrowser?&&?!window.__dui_style_registry__)?{window.__dui_style_registry__?=?{};}}//?這里才是重點,在不同的端存儲的地方不一樣public?get?registry()?{if?(isBrowser)?{return?window.__dui_style_registry__;}?else?{return?this.nodeRegistry;}}public?get?length()?{return?Object.keys(this.registry).length;}public?set(key:?string,?bundledsBy:?string[])?{this.registry[key]?=?bundledsBy;}public?get(key:?string)?{return?this.registry[key];}public?add(key:?string,?bundledBy:?string)?{if?(!this.registry[key])?{this.registry[key]?=?[];}this.registry[key].push(bundledBy);} }

    3.6 公用組件庫 UserAgent

    騰訊文檔里封裝了公用的判斷代碼運行環境的組件庫 UserAgent。雖然自執行的模塊在架構設計上會帶來混亂,因為很有可能隨著調用地方的增多,你完全不知道模塊在什么樣的時機被以什么樣的值初始化。對于 SSR 來說就很怕這種自執行的邏輯,因為如果模塊里有不支持 Node 環境的代碼,意味著你要么得改模塊,要么不用,而不能只是屏蔽初始化。

    但是這個庫仍然得支持自執行,因為這個被引用得如此廣泛,而且假設你要 ua.isMobile 這樣使用,難道得每個文件內都 const ua = new UserAgent() 嗎?這個庫原來讀取了 window.navigator.userAgent,為了里面的函數仍然能準確地判斷運行環境,在 vm 虛擬機里通過讀取 HTTP 頭,提供了 global.navigator.userAgent ,在模塊內兼容了這種情況。

    3.7 客戶端存儲

    有個場景是列表頭有個篩選器,當用戶篩選了后,會將篩選選項存在 localStorage,刷新頁面后,仍然保留篩選項。對于這個場景,在服務端直出的頁面當然也是需要篩選項這個信息的,否則就會出現直出的頁面已經呈現給用戶后。但是我們在服務端如何知道 localStorage 的值呢?換個方式想,如果我們在設置 localStorage 的時候,同步設置 localStorage 和 cookie,服務端從 cookie 取值是否就可以了。

    class?ServerStorage?{getItem(key:?string)?{if?(__SERVER__)?{return?getCookie(key);}return?localStorage.getItem(key);}setItem(key:?string,?value:?string)?{if?(__SERVER__)?{return;}localStorage.setItem(key,?value);setCookie(key,?value,?365);} }

    還有個場景是基于文件夾來存儲的,即用戶當前處于哪個文件夾下,就存儲當前文件夾下的篩選器。如果像客戶端一樣每個文件夾都存的話,勢必會在 cookie 里制造很多不必要的信息。為什么說不必要?因為其實服務端只關心上一次文件夾的篩選器,而不關心其他文件夾的,因為它只需要直出上次文件夾的內容就可以了。因此這種邏輯我們就可以特殊處理,用同一個 key 來存儲上次文件夾的信息。在切換文件夾的時候,設置當前文件夾的篩選器到 cookie 里。

    3.8 虛擬列表

    3.8.1 react-virtualized

    騰訊文檔列表頁為了提高滾動性能,使用 react-virtualized 組件。而且為了支持動態高度,還使用了 AutoSizer, CellMeasurer 等組件。這些組件需要瀏覽器寬高等信息來動態計算列表項的高度。但是在服務端上,我們是無法知道瀏覽器的寬高的,導致渲染的列表高度是 0。

    3.8.2 Client Hints

    雖然有項新技術 Client Hints可以讓服務端知道屏幕寬度,視口寬度和設備像素比(DPR),但是瀏覽器的支持度并不好。

    即使有 polyfill,用 JS 讀取這些信息,存在 cookie 里。但是我們想如果用戶第一次訪問呢?勢必會沒有這些信息。再者即使是移動端寬高固定的情況,如果是旋轉屏幕呢?更不用說 PC 端可以隨意調節瀏覽器寬高了。因此這完全不是完美的解決方案。

    3.8.3 使用 CSS 自適應

    如果我們將虛擬列表渲染的項單獨渲染而不通過虛擬列表,用 CSS 自適應寬高呢?反正首屏直出的情況下是沒有交互能力的,也就沒有滾動加載列表的情況。甚至因為首屏不可滾動,我們在移動端還可以減少首屏列表項的數目以此來減少 CGI 數據。

    function VirtualListServer<T>(props: VirtualListProps<T>) {return (<div className="pc-virtual-list">{props.list.map((item, index) => (props.itemRenderer && props.itemRenderer(props.list[index], index)))}{!props.bottomText? null: <div className="pc-virtual-list-loading" style={{ height: 60 }}>{props.bottomText}</div>}</div>); }const VirtualList = __SERVER__ ? VirtualListServer : VirtualListClient;

    3.9 不可序列化對象

    本來這個小章節算是原 CSR 代碼里實現的問題,但是涉及的邏輯較多,因此也只是在運用數據前來做轉換。

    前面說過我們會往文檔里以全局變量的方式注入 state,怎么注入?其實就是用 JSON.stringify 將 state 序列化成字符串,如果這時候 state 里包含了函數呢?那么函數就會丟失。(不過看到下一小章節你會發現 serialize-javascript 是有保留函數的選項的,只是我覺得 state 應該是純數據,正確的做法應該是將函數從 state 里移除,兩種方式自由取舍吧)

    例如這里的 pageRange,里面包含了 add,getNext 等方法,在數據注入到客戶端后,就只剩下純數據:

    const?getDefaultList?=?()?=>?({list:?[],loading:?true,p:?false,allObtained:?false,pageRange:?new?PageRange({?start:?-listLimit,?limit:?listLimit?}),scrollTop:?0, });

    在客戶端使用的時候,還需要將 pageRange 轉成新的實例:

    export?function?pageRangeTransform(opt:?PageRange)?{if?(typeof?opt.add?===?'function')?{return?opt;}return?new?PageRange(opt); }

    3.10 引用類型的 state

    還遇到一個比較有趣的問題如下圖:

  • db 是一個內存上的數據對象,用來存儲列表等相關的數據的,而 state 里的列表其實只是 db 里的一個引用;

  • 在更新列表數據的時候,發送了 CGI,其實是更新了 db 里的列表數據;

  • 在更新列表項是否可編輯的數據的時候,其實也是更改的 db 里的數據,然后通過一個 forceTime 來強制 state 更新視圖;

  • 這對于加入了 SSR 的 CSR 來說會有幾點問題:

  • 因為我們復用了服務端注入的數據,省去了 CGI 的步驟,在客戶端上也就沒有往 db 里添加列表數據;

  • state 里的列表數據不再是引用的 db 里的數據,因此更新 forceTime,是強制不了 state 更新視圖的;

  • 兩個典型的 Bug(代碼里寫了注釋,應該不用再解釋了):

    /* *?如果有 preloadState,需要調用 db 來設置一下數據。有一個問題是: *?1.?CSR?列表的?0-30?的數據是通過?API?拉取的,在?API?里通過?db?設置了?0-30?的數據 *?2.?SSR?0-30?的數據是通過?preloadedState?注入到客戶端的,沒有通過?db?設置?0-30?的數據 *?3.?列表往下拉的時候,通過?CGI?拉取?30-60?的數據,這時候通過?db?合并,會丟失?0-30?的數據 */ if?(preloadedState)?{const?db?=?getDBSingleton();if?(preloadedState.list?&&?preloadedState.list.recent)?{const?transedList?=?transformForInitialState(preloadedState.list.recent.list);preloadedState.list.recent.list?=?db.register(ListTypeInStore.recent,?transedList);} } if?(preloadedState.folderUI?&&?preloadedState.folderUI.viewStack.length)?{const?folderData?=?preloadedState.folderUI.viewStack[0];const?{?folderID,?list?}?=?folderData;if?(list?&&?list.length)?{/**?為什么要用 db.register 返回的 list 重新賦值?因為客戶端上的 state 引用的是 db 里的數據,在調用* forceUpdate 的時候只是更新了個時間,如果這里不保持一致,在調用 forceUpdate 的時候就不會更新了。*?典型的?Bug,按了右鍵重命名無效*/folderData.list?=?registerDBForInitialState(folderID,?transformForInitialState(list));} }

    3.11 安全

    使用字符串拼接的方式插入初始化的 state,需要轉義而避免 xss 攻擊。我們可以使用 serialize-javascript 庫來轉義數據。

    import?serialize?from?'serialize-javascript';export?function?injectDataToClient(key:?string,?data:?any)?{const?serializedData?=?serialize(data,?{isJSON:?true,ignoreFunction:?true,});return?`<script>window["${key}"]?=?${serializedData}</script>`; }export?function?getStateScriptTag(initialState:?any)?{return?injectDataToClient('__initial_state__',?initialState); };

    3.12 服務端路由

    對于單頁面來說,使用 react-router 來管理路由,服務端也需要直出相對于的組件。需要做的只是將路由組件換成 StaticRouter ,通過 localtion提供頁面地址和 context 存儲上下文 。

    import { StaticRouter as Router } from 'react-router-dom';(async () => {const routerContext = {};const bodyHtml = ReactDOMServer.renderToString(<Router basename={'/desktop'} location={TSRENV.href} context={routerContext} ><Provider store={store}><ServerIndex /></Provider></Router>); })();

    4. 運行環境

    4.1 網絡

    4.1.1 網絡請求

    當瀏覽器發起 CGI 請求,形如 https://docs.qq.com/cgi-bin/xxx,不僅需要解析 DNS,還需要建立 HTTPS 鏈接,還要再經過公司的統一網關接入層。如果我們的 SSR 服務同部署在騰訊云上,是否有請求出去繞一圈再繞回來的感覺?因為我們的服務都接入了 L5(服務發現和負載均衡),那么我們可以通過解析 L5 獲得 IP 和端口,以 HTTP 發起請求。

    以兼容 L5 的北極星 SDK 來解析(cl5 需要依賴環境,在我使用的基礎鏡像 tlinux-mini 上會有錯誤)。

    PS: Axios 發送 HTTPS 請求會報錯,因此在 Node 端換成了 Got,方便本地開發。

    const?{?Consumer?}?=?require('@tencent/polaris'); const?consumer?=?new?Consumer();async?function?resolve(namespace,?service)?{const?response?=?await?consumer.select(namespace,?service);if?(response)?{const?{instance:?{?host,?port?},}?=?response;return?`${host}:${port}`;}return?null; }

    需要注意的是,北極星的第一次解析比較耗時,大概 200ms 的樣子,因此應該在應用啟動的時候就調用解析一次,后續再解析就會是 1~3ms 了。

    這里還有個點是我們應該請求哪個 L5?假設有兩個 CGI,doclist 和 userInfo,我們是解析它們各自的 L5,通過 OIDB 的協議請求嗎?考慮三個方面:

  • 這里詢問了文檔后臺,通過 OIDB 并沒有比通過 HTTP 協議快多少;

  • 我們需要一直維護 CGI 和 L5 的對應關系,如果后臺重構,信息同步不到位,換了新的 L5,服務將會掛掉;

  • 沒有更新 xsrf 的邏輯;

  • 好在文檔還有個統一的接入層 tsw,因此我們其實只需要解析接入層 tsw 的 L5,將請求都發往它就可以了。

    4.1.2 cookie

    在 SSR 代發起 CGI 請求,不僅需要從請求取出客戶端傳遞過來的 cookie 來使用,在我們的 tsw 服務上,還會驗證 csrf,因此 SSR 發出 CGI 請求后,可能 tsw 會更新 csrf,因此還需要將 CGI 請求返回的 set-cookie 再設置回客戶端。

    const?setCookie?=?require('set-cookie-parser');function?setCookies(cookis)?{const?parsedCookies?=?setCookie.parse(cookis?||?[])?||?[];if?(ctx.headerSent)?{return;}parsedCookies.map((cookieInfo)?=>?{const?{?name,?value,?path,?domain,?expires,?secure,?httpOnly,?sameSite?}?=?cookieInfo;try?{ctx.cookies.set(name,?value,?{overwrite:?true,path,domain,expires,secure,httpOnly,sameSite,});}?catch?(err)?{logger.error(err);}}); }

    overwrite 設置為 true 是因為當我們有多個 CGI 請求,所返回的同名 set-cookie如果不覆蓋的話,會使得 SSR 返回的 HTTP 頭很大。

    還要說說 secure 參數。這個參數表示 cookie 是否只能是 HTTPS 下傳輸。我們的應用是在 tsw 服務之后的,一般來講也都會在 nginx 之后以 http 提供服務。那么我們就設置不了這個 secure 參數。如果要設置的話,需要有兩步:

  • 初始化 koa 的時候,設置 proxy;

    const?app?=?new?Koa({?proxy:?true?})
  • koa 前面的代理設置 X-Forwarded-Proto 頭部,表明是工作在 HTTPS 模式下;

  • 但是實際上在我的服務里沒有收到這個頭部,因此仍然會報錯,由于我們沒法去改 tsw,也很清楚地知道我們是工作在代理之后,有個解決方案:

    this.app.use(async?(ctx,?next)?=>?{ctx.cookies.secure?=?true;await?next(); });

    4.2 并發和上下文隔離

    我們來考慮這樣一種情況:

    當有兩個請求 A 和 B 一前一后到達 Server,在經過一大串的異步邏輯之后。到達后面的那個處理邏輯的時候,它怎么知道它在處理哪個請求?方法當然是有:

  • 把 koa 的 ctx 一層一層傳遞,只要有涉及到具體請求的函數,都傳遞一下 ctx(是不是瘋狂?);

  • 或者把 ctx 存在 state 里,需要 ctx 的話從 state 里取(先不說這違反了 state 里應該放純數據的原則,如果是一些工具函數呢?比如 getCookie 這樣的函數,讓它的 cookie 從哪里取?想想是不是頭大?);

  • 因此我們需要想個辦法,將 A 和 B 的請求隔離開來。

    4.2.1 cluster 和 worker

    如果說要隔離請求,我們可以有 cluster 模塊提供進程粒度的隔離,也可以通過 worker_threads 模塊提供線程粒度的隔離。但是難道我們一個進程和一個線程同時只能處理一個請求,只有一個請求完全返回結果后才能處理下一個嗎?這顯然是不可能的。

    但是為了下面的錯誤捕獲問題,我確實用 worker_threads + vm 嘗試了好幾種方法,雖然最后都放棄了。并且因為使用 worker_threads 可以共享線程數據的優點在這個場景下并沒有多大的應用場景,反而是 cluster 可以共享 TCP 端口,最后是用 cluster + vm ,不過這是后話了。

    4.2.2 domain

    上下文隔離的技術,從 QQ 空間團隊 tsw 那里學了個比較騷的方法,主要有兩個關鍵點:

  • process.domain 總是指向當前異步流程的上下文,因此可以將需要的數據掛載到 process.domian 上;

  • 用 Object.defineProperty 設置數據的 getter 和 setter 函數,保證操作到的是 process.domain 上的對應數據;

  • 用簡短的代碼演示就是這樣的:

    const?domain?=?require('domain');Object.defineProperty(global,?'myVar',?{get:?()?=>?process.domain.myVar,set:?(value)?=>?{process.domain.myVar?=?value;}, });const?handler?=?(label)?=>?{setTimeout(()?=>?{console.log(`${label}:?${global.myVar}`);},?(1?+?Math.random()?*?4)?*?1000); };for?(let?i?=?0;?i?<?3;?i++)?{const?d?=?domain.create();d.run(()?=>?{global.myVar?=?i;handler(`test-${i}`);}); }//?test-1:?1 //?test-0:?0 //?test-2:?2

    但是這個方案存在什么樣的問題?

  • domain 沒法保證雖然對象在它 run 函數里初始化,process.domain 一定有值,也可能是 undefined;

  • requre 過的文件,被 cache 了,需要執行清除緩存的操作,重新 require。雖然可以用 defineProperty 來定義值,但是如果有的模塊是 const moduleVar = global.myVar; module.exports = moduleVar; 沒有重新執行的話,導出的值將是錯誤的;

  • 4.3 vm

    上下文隔離,我們還可以用 vm 來做。(然后我們的挑戰就變成了怎么把十幾萬行的代碼放在 vm 里跑,為什么需要把十幾萬行代碼都放進去?因為后面會說到被 require 的模塊里訪問 global 的問題,雖然后面的后面解決了這個問題)

    vm 的一個基本使用姿勢是這樣的:

    const?vm?=?require('vm');const?code?=?'console.log(myVar)';vm.runInNewContext(code,?{myVar:?'AlloyTeam',console, });//?AlloyTeam

    功能是不是很像 eval?,使用 eval 的話:

    let?num?=?1; eval('num?++'); console.log(num);//?2

    使用 Function 的話:

    /***?@file?function.js*/ global.num?=?1; (new?Function('step',?'num?+=?step'))(1); console.log(num);//?node?function.js //?>2

    細心的讀者可能會發現,Function 的例子里,我寫的是 global.num = 1 而不是 let num = 1,這是為什么?

  • 由 Function 構造器創建的函數不會創建當前環境的閉包,而是被創建在全局環境里;

  • 我這里的代碼寫在 function.js 文件里,是當做一個模塊被運行的,是在模塊的作用域里;

  • 基于以上 2 點,Function 里的代碼能訪問到的變量就是 global 和它的局部變量 step ,如果寫成 let num = 1 將會報錯;

  • 使用 evel 和 Function 可以做到嗎?感覺理論上像是可以的,假設我們給每個請求分配 ID,使用 Object.defineProperty 來定義數據的存取。但是我沒有試過,而是使用成熟的 vm 模塊,好奇的讀者可以試一下。

    另外因為我們并沒有運行外部的代碼,要在 vm 里跑的都是業務代碼,因此不關心 vm 的進程逃逸問題,如果有這方面擔憂的可以用 vm2。

    4.3.1 global

    我們在 Node 環境下訪問全局變量,有兩種方式:

    (()?=>?{a?=?1;global.b?=?2; })();console.log(a); console.log(b);//?1 //?2

    而在 vm 里,是沒有 global 的,考察以下代碼:

    const?vm?=?require('vm');global.a?=?1;const?code?=?`console.log(typeof?global);console.log(typeof?a); `;vm.runInNewContext(code,?{console, });//?undefined //?undefined

    因此假設我們要支持代碼里能夠以 global.myVar 和 myVar 兩種方式來訪問上下文里的全局變量的話,就要構造出一個 global 變量。

    上下文的全局變量默認是空的,不僅 global 沒有,還有一些函數也沒有,我們來看看最終構造出的上下文是都有什么:

    async?getVMContext(renderJSFile)?{const?pathInfo?=?path.parse(renderJSFile);//?模塊系統的變量const?moduleGlobal?=?{__filename:?renderJSFile,__dirname:?pathInfo.dir,};const?commonContext?=?{Buffer,process,console,require,exports,module,};/*?業務上定義的的全局對象,運行的時候會重新賦值*?{*?????window:?undefined,*?????navigator:?{*?????????userAgent:?'',*?????},*?????location:?{*?????????search:?'',*?????},*?}*/const?browserGlobal?=?renderConfig.vmGlobal(renderJSFile);return?vm.createContext({...commonContext,...moduleGlobal,...global,...browserGlobal,//?重寫?global?循環變量global:?{...browserGlobal,},}); }

    4.3.2 require

    前面說到 vm 的上下文默認是空的,然后我們給它傳遞了 module,exports,require,那么它能 require 外部模塊了,但是被 require 的模塊如果訪問 global,會是 vm 里我們創建的 global,還是宿主環境下的 global 呢?

    我們有個文件 vm-global-required.js 是要被 require 的:

    const?myVar?=?global.myVar;console.log('[required-file]:',?myVar);

    我們還有個文件是宿主環境:

    const?vm?=?require('vm');global.myVar?=?1;const?code?=?`console.log("[vm-host]:",?global.myVar);require('./vm-global-required'); `;vm.runInNewContext(code,?{global:?{myVar:?2,},console,require, });

    運行代碼,結果是:

    //?[vm-host]:?2 //?[required-file]:?1

    可以看到被 require 的模塊所訪問的 global 并不是 vm 定義的上下文,而是宿主環境的 global。

    4.3.3 代碼編譯緩存

    以 vm 創建的代碼沙箱是需要編譯的,我們不可能每個請求過來都重復編譯,因此可以在啟動的時候就提前編譯緩存:

    compilerVMByFile(renderJSFile)?{const?scriptContent?=?fileManage.getJSContent(renderJSFile);if?(!scriptContent)?{return;}const?scriptInstance?=?new?vm.Script(scriptContent,?{filename:?renderJSFile,});return?scriptInstance; }getVMInstance(renderJSFile)?{if?(!this.vmInstanceCache[renderJSFile])?{const?vmInstance?=?this.compilerVMByFile(renderJSFile);this.vmInstanceCache[renderJSFile]?=?vmInstance;}return?this.vmInstanceCache[renderJSFile]; }

    但是其實 v8 編譯是不編譯函數體的,好在可以設置一下:

    const?v8?=?require('v8'); v8.setFlagsFromString('--no-lazy');

    (編譯部分還嘗試過 createCachedData,可以詳見以下錯誤捕獲的使用 filename 章節)

    4.3.4 超時

    vm 運行的時候可以設置 timeout 參數控制超時,當超過時間后會報錯:

    const?vm?=?require('vm');const?vmFunc?=?new?vm.Script(`while(1)?{} `);try?{vmFunc.runInNewContext({http,console,},?{timeout:?100,}) }?catch?(err)?{console.log('vm-timeout'); }//?vm-timeout

    但是它的超時真的有效嗎?我們來做個試驗。如以下代碼:

  • 設置了 timeout 是 100;

  • 用 process 監聽了錯誤,如果超時觸發了錯誤,process 就會捕獲到錯誤輸出出來;

  • /timeout-get 在 2000ms 后才返回結果;

  • const?Koa?=?require('koa'); const?Router?=?require('koa-router'); const?vm?=?require('vm'); const?http?=?require('http');const?app?=?new?Koa();const?router?=?new?Router();router.get('/timeout-get',?async?(ctx)?=>?{await?new?Promise((resolve)?=>?{setTimeout(()?=>?{ctx.body?=?'OK';resolve();},?2000);}); });app.use(router.routes()).use(router.allowedMethods());app.listen(3000);process.on('unhandledRejection',?(err)?=>?{console.log('unhandledRejection',?err); });process.on('uncaughtException',?(err)?=>?{console.log('uncaughtException',?err); });console.time('http-cost');const?vmFunc?=?new?vm.Script(`http.get('http://127.0.0.1:3000/timeout-get',?(res)?=>?{const?{?statusCode?}?=?res;console.log('statusCode:',?statusCode);console.timeEnd('http-cost');process.exit(0);})` );vmFunc.runInNewContext({http,console,process, },?{timeout:?100,microtaskMode:?'afterEvaluate', })console.log('vm-executed');

    輸出結果是什么呢?

    vm-executed statusCode:?200 http-cost:?2016.098ms

    說明 vm 的這個 timeout 參數在我們的場景下是不一定有效的,因此我們還需要在宿主環境額外設置超時返回。

    4.4 錯誤捕獲

    我們的 SSR 和普通的后臺服務最大的區別在于什么?我想是在于我們不允許返回空內容。后臺的 CGI 服務在錯誤的時候,返回個錯誤碼,有前端來以更友好的方式展示錯誤信息。但是 SSR 的服務,即使錯誤了,也需要返回內容給用戶,否則就是白屏。因此錯誤的捕獲顯得尤為重要。

    總結一下背景的話:

  • vm 所執行的代碼可能來自于第三方,但是整個項目是提供基礎鏡像,第三方基于鏡像自行部署的,因此不關心 vm 里的代碼安全問題,不用用到 vm2

  • vm 里的代碼是有可能出錯的,錯誤可能來自于同步代碼、異步代碼或者未處理的 Promise 錯誤

  • vm 代碼是異步并行的,假設每次執行 vm 代碼都有一個 id

  • vm 里的代碼即使出錯,也必須要知道是哪個 id 的 vm 代碼執行出錯了,來執行兜底的策略

  • 4.4.1 process 捕獲

    在 node 里,如果要捕獲未知的錯誤,我們當然可以用 process 來捕獲

    process.on('unhandledRejection',?(err)?=>?{//?do?something });process.on('uncaughtException',?(err)?=>?{//?do?something });

    這代碼不僅可以捕獲同步、異步錯誤,也能捕獲 Promise 錯誤。但同時,我們從 err 對象上也獲取不了出錯時候的上下文信息。像背景里的要求,就不知道是哪個 id 的 vm 出錯了

    4.4.2 try...catch

    如果以 vm 來執行代碼的話,我們大可以在代碼的外部包裹 try...catch 來捕獲異常。看下面的例子,try...catch 捕獲到了錯誤,錯誤就沒再冒泡到 process。

    const?vm?=?require('vm');process.on('uncaughtException',?(err)?=>?{console.log('[uncaughtException]:',?err); });const?script?=?new?vm.Script(`try?{throw?new?Error('from?vm')}?catch?(err)?{console.log(err)} `);script.runInNewContext({?Error,?console?});//?Error:?from?vm //?????at?evalmachine.<anonymous>:3:15

    4.4.3 異步錯誤

    改寫上面的例子,將錯誤在異步函數里拋出,try...catch 捕獲不到錯誤,錯誤冒泡到 process,被 uncaughtException 事件捕獲到

    const?vm?=?require('vm');process.on('uncaughtException',?(err)?=>?{console.log('[uncaughtException]:',?err); });process.on('unhandledRejection',?(err)?=>?{console.log('[unhandledRejection]:',?err); });const?script?=?new?vm.Script(`try?{setTimeout(()?=>?{throw?new?Error('from?vm')})}?catch?(err)?{console.log(err)} `);script.runInNewContext({?Error,?console,?setTimeout?});//?[uncaughtException]:?Error:?from?vm //?????at?Timeout._onTimeout?(evalmachine.<anonymous>:4:19)

    那有什么辦法捕獲異步錯誤嗎?辦法還是有的,node 里有個 domain 模塊,可以用來捕獲異步錯誤。(雖然已經標記為廢棄狀態,但是已經用 async_hooks 重寫了,意味著即使真的被廢棄,也能自己實現一個)

    繼續改寫上面的例子,將 vm 放在 domain 里執行,可以看到錯誤被 domain 捕獲到了

    const?vm?=?require('vm'); const?domain?=?require('domain');process.on('uncaughtException',?(err)?=>?{console.log('[uncaughtException]:',?err); });process.on('unhandledRejection',?(err)?=>?{console.log('[unhandledRejection]:',?err); });const?script?=?new?vm.Script(`try?{setTimeout(()?=>?{throw?new?Error('from?vm')})}?catch?(err)?{console.log(err)} `);const?d?=?domain.create();d.on('error',?(err)?=>?{console.log('[domain-error]:',?err); });d.run(()?=>?{script.runInNewContext({?Error,?console,?setTimeout?}); });//?[domain-error]:?Error:?from?vm //?????at?Timeout._onTimeout?(evalmachine.<anonymous>:4:19)

    4.4.4 Promise 錯誤

    但是假如將上一個例子的 vm 代碼改成 Promise 執行呢?domain 捕獲不到錯誤,錯誤冒泡到 process 上

    const?vm?=?require('vm'); const?domain?=?require('domain');process.on('uncaughtException',?(err)?=>?{console.log('[uncaughtException]:',?err); });process.on('unhandledRejection',?(err)?=>?{console.log('[unhandledRejection]:',?err); });const?script?=?new?vm.Script(`Promise.resolve().then(()?=>?{throw?new?Error('notExistPromiseFunc')}) `);const?d?=?domain.create();d.on('error',?(err)?=>?{console.log('[domain-error]:',?err); });d.run(()?=>?{script.runInNewContext({?Error,?console,?setTimeout?}); });//?[unhandledRejection]:?Error:?notExistPromiseFunc //?????at?evalmachine.<anonymous>:3:15

    為什么?node 文檔里是這么說的

    Domains will not interfere with the error handling mechanisms for promises. In other words, no 'error' event will be emitted for unhandled Promise rejections.

    那有什么辦法嗎?這里想了兩個比較騷的寫法。

    4.4.4.1 使用 filename

    我們知道 vm 在執行的時候,是可以提供一個 filename 屬性,在錯誤的時候,會被添加到錯誤堆棧內。默認值是 'evalmachine.<anonymous>' 也就是我們上面的錯誤經常看到的第二行代碼錯誤的位置。這就帶來了操作的空間。

    const?vm?=?require('vm'); const?markStart?=?'<vm-error>'; const?markEnd?=?'</vm-error>';const?getContext?=?()?=>vm.createContext({console,process,setTimeout,});const?parseErrorStack?=?(err)?=>?{const?errorStr?=?err.stack;const?valueStart?=?errorStr.indexOf(markStart);const?valueEnd?=?errorStr.lastIndexOf(markEnd);if?(valueStart?!==?-1?&&?valueEnd?!==?-1)?{return?errorStr.slice(valueStart?+?markStart.length,?valueEnd);}console.log('[parse-error]');return?null; };process.on('unhandledRejection',?(err)?=>?{console.log('[unhandledRejection]:',?parseErrorStack(err)); });process.on('uncaughtException',?(err)?=>?{console.log('[uncaughtException]:',?parseErrorStack(err)); });const?getScript?=?(flag)?=>?{const?filename?=?`${markStart}${flag}${markEnd}`;return?new?vm.Script(`(()?=>?{new?Promise((resolve,?reject)?=>?{setTimeout(()?=>?{reject(new?Error('${flag}'));},?100)})})()`,{?filename?}); };(async?()?=>?{for?(let?i?=?0;?i?<?3;?i++)?{await?getScript(i).runInContext(getContext());} })();//?[unhandledRejection]:?0 //?[unhandledRejection]:?1 //?[unhandledRejection]:?2

    看下上面的代碼結構,我們做了幾件事:

  • 在 vm 代碼編譯的時候,以 vm-error 標識符標記了我們要傳遞到錯誤堆棧的值

  • 在 process 捕獲 Promise 錯誤

  • 在 process 捕獲到 Promise 錯誤的時候,從錯誤堆棧上根據標識符解析出我們要的值

  • 但是這樣的代碼存在什么問題?

    最主要的問題在于 filename 是編譯進去的,即使生成 v8 代碼緩存的 Buffer,后面用這個 Buffer 來編譯一個新的 script 實例,傳遞進新的 filename,仍然改變不了之前的值。所以會帶來代碼每次都需要編譯的成本。

    我們可以來實踐以下:

    const?vm?=?require('vm'); require('v8').setFlagsFromString('--no-lazy');const?markStart?=?'<vm-error>'; const?markEnd?=?'</vm-error>';const?getContext?=?myVar?=>?vm.createContext({console,process,setTimeout,myVar, });const?parseErrorStack?=?(err)?=>?{const?errorStr?=?err.stack;const?valueStart?=?errorStr.indexOf(markStart);const?valueEnd?=?errorStr.lastIndexOf(markEnd);if?(valueStart?!==?-1?&&?valueEnd?!==?-1)?{return?errorStr.slice(valueStart?+?markStart.length,?valueEnd);}console.log('[parse-error]');return?null; };process.on('unhandledRejection',?(err)?=>?{console.log('[unhandledRejection]:',?parseErrorStack(err)); });process.on('uncaughtException',?(err)?=>?{console.log('[uncaughtException]:',?parseErrorStack(err)); });const?getFileName?=?flag?=>?`${markStart}${flag}${markEnd}`;const?code?=?` (()?=>?{new?Promise((resolve,?reject)?=>?{setTimeout(()?=>?{reject(new?Error(myVar));},?100)}) })() `;const?scriptCache?=?new?vm.Script(code,?{filename:?getFileName(-1), });const?scriptCachedData?=?scriptCache.createCachedData();const?getScript?=?flag?=>?new?vm.Script('?'.repeat(code.length),?{filename:?getFileName(flag),cachedData:?scriptCachedData, });(async?()?=>?{for?(let?i?=?0;?i?<?3;?i++)?{await?getScript(i).runInContext(getContext(i));} })();

    看上面的代碼,對比上一個例子,主要有這幾個改動:

  • 緩存了 vm 代碼編譯后的實例,filename 設置的 -1

  • 循環內的 flag 標志是通過 myVar 注入到 vm 的全局變量,在 vm 里 throw 這個 flag 錯誤值的

  • 循環內的 vm 執行,filename 設置的 0 - 3

  • 結果:編譯后的代碼實例并不會因為使用 cachedData 重新編譯后,filename 就會被改變,因此就無法使用 cacheData + filename 的方式來既要減少編譯時間又想要自定義錯誤堆棧。

    4.4.4.2 重寫 Promise

    當我們想同步和異步代碼都能捕獲得到,那么只剩下 Promise 錯誤了。什么情況會報 Promise 未處理的錯誤呢?也就是沒有寫 catch 的情況。那么如果我們改寫 Promise ,將每個 Promise 都加上一個默認的 catch 函數,是否能達到期望呢?

    const?vm?=?require('vm');let?processFlag;process.on('unhandledRejection',?(err)?=>?{console.log('[unhandledRejection-processFlag]:',?processFlag); });const?getVMPromise?=?(flag)?=>?{const?vmPromise?=?function?(...args)?{const?p?=?new?Promise(...args);p.then(()?=>?{},(err)?=>?{processFlag?=?flag;throw?err;});return?p;};['then',?'catch',?'finally',?'all',?'race',?'allSettled',?'any',?'resolve',?'reject',?'try'].map((key)?=>?{if?(Promise[key])?{vmPromise[key]?=?Promise[key];}});return?vmPromise; };const?getContext?=?(flag)?=>vm.createContext({Promise:?getVMPromise(flag),console,setTimeout,});const?getScript?=?(flag)?=>?{return?new?vm.Script(`new?Promise((resolve,?reject)?=>?{setTimeout(()?=>?{console.log("[vm-current-task]:",?"${flag}");reject()},?(1?+?Math.random()?*?4)?*?1000);})`); };for?(let?i?=?0;?i?<?3;?i++)?{getScript(i).runInContext(getContext(i)); }//?[vm-current-task]:?0 //?[unhandledRejection-processFlag]:?0 //?[vm-current-task]:?2 //?[unhandledRejection-processFlag]:?2 //?[vm-current-task]:?1 //?[unhandledRejection-processFlag]:?1

    考察以上的代碼,我們做了這些事:

  • 改寫了 Promise,在 Promise 添加了第一個 then 方法來處理錯誤

  • 在自定義的 Promise 的第一個 then 方法里存儲了當前異步任務的上下文

  • 將自定義的 Promise 當做全局變量傳遞給 vm

  • 結果:在一個隨機的任務 ID 上,成功在 process 上捕獲到了上下文的信息。(但是 Promise 實現的精華在于 then 之后的鏈式調用,這在上面的代碼是沒有體現的。)

    4.4.5 必要性思考

    重寫 Promise 的方案可行嗎?看起來是可行的,但其實最后也沒有用這個方案(其實是我還沒實施。。。)。因為假設我一個 32 核的 Pod,fork 出 32 個進程處理請求,平均分到每個進程的請求同一時間也不會很多。而出錯是應該在編碼和系統測試就應該避免的,或者自動化測試,或者生成骨架屏時避免。如果要同時捕獲這三個錯誤,需要在異步代碼都使用 domain 捕獲(可能會有性能問題)和 Promise 記錄上下文。其實我們可以在出錯的時候將當前進程所處理的所有請求直接返回原文檔,回退到無 SSR 的狀態。(不過 Promise 的方案仍然值得研究嘗試一下,會發大篇幅也是因為之前陷進去了這個問題,研究了好一段時間)

    4.5 重定向

    登錄態的問題和文檔強相關,但是仍然想要拋出來和大家探討一下重定向的這個問題。

    騰訊文檔的登錄態在前端是無法完全判斷的,只有兩種最基本的情況前端是知道沒有登錄態:

  • 沒有 cookie;

  • cookie 里沒有 uid 和 uid_key;

  • 如果是登錄態過期,那么只能是在發起 CGI 請求,后臺返回具體的錯誤碼之后才知道。所以 CSR 的登錄是在列表頁顯示,并且正常渲染的情況下,發現 CGI 有具體的登錄態錯誤碼了,就動態加載登錄模塊來提醒用戶登錄。整個的效果就是這樣的:

    4.5.1 rewrite

    當我們引入了 SSR 后,發送 CGI 請求遇到特定的登錄態錯誤碼我們是知道的。那么我們為什么不直接返回登錄頁就可以了呢?很簡單,直接 ctx.redirect 302 重定向到登錄頁就可以了,但是問題來了:

  • 我們的 PC 端沒有獨立的登錄頁,是用動態加載模塊的方式來在當前頁面展示登錄框的;

  • 需要處理 URL 跳轉的問題,不僅是從外部跳轉過來的帶有登錄態的 URL,還要處理登陸完后的 URL 跳轉問題;

  • 登錄的模塊在其他的庫,就需要去改到那個庫發布才可以;

  • 有沒有更好的方法呢?

  • 我們另外做一個很簡單的 login 頁面,這個頁面只用來做一件事,復用原來的代碼在這個頁面動態加載登錄模塊;

  • 如果用戶登錄態有效,返回請求的頁面,如果登錄態失效,就讀取 login 頁面的內容返回;

  • 這樣就做到了不用更改登錄模塊邏輯,也不會更改到鏈接地址,也就不用處理 URL 跳轉的問題。

    但是需要注意的是,因為以下會提到同時接入 SSR 服務和原 Nginx 服務,因此如果要不改變現網表現的話,login 頁面不應該被發到 Nginx 機器上。類似的還有獨立密碼的登錄頁。

    這樣實現的效果就是:

    4.5.2 redirect

    像上面的登錄態問題,在移動端上有獨立的登錄頁,那么我們就只需要用 ctx.redirect 使用 302 跳轉到對應的頁面就 OK 了。相似的應用場景還有如果是 PC 端訪問了移動端的 URL 地址,或者移動端訪問了 PC 端的地址,需要讀取 UA 來判斷訪問端和 URL 地址,跳轉到對應的頁面。

    4.5.3 小程序登錄態

    要額外提到的小程序登錄態是因為,小程序是通過小程序殼登錄,再將登錄態附加在 webviewer 里的 URL 地址上,由前端解析 URL 地址來種登錄態的。這意味著小程序登錄后,SSR 的 cookie 里是沒有登錄態的,發起 CGI 請求就會報錯。所以我們就需要做兩件事:

  • 從 URL 上解析登錄態,將登錄態信息附加到當次請求的 cookie 里,保證當次請求不會出錯,也不會因為沒有登錄態重復跳到登錄頁;

  • 設置新的具有登錄態的 cookie 到客戶端;

  • const?appendAndSetCookie?=?(ctx,?key,?value)?=>?{const?oldCookie?=?ctx.header.cookie?||?'';ctx.header.cookie?=?`${oldCookie}${oldCookie.endsWith(';')???''?:?';'}${key}=${value};`;ctx.cookies.set(key,?value); };

    5. 骨架屏

    5.1 基本實現

    回顧整個生成首屏頁面的流程:

  • 創建 redux 的 store;

  • 發送 CGI 填充 store 數據;

  • 以 store 的數據為基礎渲染 react 應用;

  • 除了發送 CGI 這一步需要在線上環境,在用戶瀏覽器發起請求時由 SSR Server 代理請求外,空的 store 和以空的 store 渲染出 React 應用,是我們在編譯期間就可以確定的。那么我們就可以很方便地獲得一個骨架屏,而所需要做的在原來 SSR 的基礎上只有幾步:

  • 創建一個空的 ctx,以復用原來的 SSR 邏輯:

    const?generateCTX?=?(renderJSFile,?renderHtmlFile)?=>?({headers:?[],url:?'',body:?'',renderJSFile,renderHtmlFile,originalUrl:?'',request:?{href:?'',}, });
  • 傳遞給應用標識當前是生成骨架屏邏輯,應用里不發送 CGI:

    if?(!TSRENV.isSkeleton)?{await?Promise.all([store.dispatch.user.requestGetUserInfo(),store.dispatch.list.refreshRecentOpenList(),]); }
  • 將生成的 HTML 寫入原文檔:

    if?(renderConfig.skeleton.writeToFile)?{fileManage.writeHtmlFile(renderHtmlFile); }

    但是這里我們考慮應該以怎樣的方式來寫入。假設原來是將 <div id="root"><div id="server-render"></div></div> 里的 server-render 整個標簽(包括 div)替換成渲染后的文檔(為什么原來不也是用注釋的方式?因為很可能編譯后會被去掉注釋)。那么我們生成的骨架屏也將這個替換掉的話,后續 SSR 找不到這個標簽。如果插入在這個標簽里面的話,顯然骨架屏生成的 DOM 在層級上和 SSR 生成的 DOM 是不一樣的。這里我們可以借助注釋。

    原來的文檔:

    <div?id="root"><div?id="server-render"></div> </div>

    骨架屏文檔(編譯完 CSR 后再生成):

    <div?id="root"><!--SKELETON-START--><div?id="ssr-root"></div><!--SKELETON-END--> </div>

    SSR 后文檔:

    <div?id="root"><div?id="ssr-root"></div> </div>

    我們能獲得的將是具有頁面框架的靜態文檔。傳統的 React 應用需要在 React 加載下來后渲染才有頁面元素,而骨架屏將在 DOM 直接返回頁面的時候就已經有內容。這在我們假設 SSR 錯誤后,返回未直出文檔的情況下,也比原來的返回空白頁面觀感上好很多。或者我們將類似的邏輯遷移到其他的頁面上,即使不做 SSR,也可以在靜態編譯的時候生成骨架屏,在幾十毫秒內就能結束白屏時間。

  • 5.2 白屏時間思考

    我們引入了 SSR,好處當然是首屏時間會大大降低,但是同時白屏時間會增加。有辦法解決嗎?

  • <紅色箭頭部分> 瀏覽器請求前端 HTML 頁面,Server 返回骨架屏,同時在骨架屏內注入 CGI 請求的 JS。這樣我們可以以近乎靜態資源請求的速度獲得極低的白屏時間;

  • <藍色箭頭部分> Server 在返回骨架屏的同時,開始 SSR 的渲染;

  • <綠色箭頭部分> 注入到骨架屏的 JS 開始發起 CGI 請求,這個請求不是到后端的 Go Server,而是到我們的 SSR Server,SSR 返回渲染后的 DOM 節點字符串,前端直接注入到頁面渲染;

  • 這個方案能給我們帶來什么?

  • 極低的白屏時間;

  • 相對于 SSR 更短的響應耗時(但是總的首屏時間會稍微長一點點),因為 SSR 的響應耗時將會減少 Server 返回骨架屏到瀏覽器再次發起 SSR CGI 的時間;

  • 有采用嗎?沒有。因為在重定向部分說到我們有一個比較嚴重的登錄體驗問題,如果使用了這個方案,那么又會變成先訪問了列表頁才出現登錄的問題。而考察現網的數據,訪問了列表頁的大概有 20% 用戶是未登錄狀態,那么我們就不能采用這個方案。但也算是一個研究,供參考。

    6. 性能測速

    當我們做了 SSR,當然關心能夠給我們的業務帶來多少的性能提升,這里我主要關注這幾點:

    6.1 首屏時間

    引入 SSR 我們最主要的目的就是為了降低首屏時間。這里因為我們知道列表是最慢也是最主要的頁面資源,因此以列表加載的時間為準。假設沒有引入 SSR,我們的首屏時間是這么算的。以列表第一次渲染的時間為準:

    //?speed.ts let?hasReport?=?false;const?openSpeedReport?=?()?=>?{if?(hasReport)?{return;}hasReport?=?true;console.log((new?Date()).getTime()?-?performance.timing.navigationStart); };//?list.tsx useEffect(()?=>?{//?因為一開始可能是沒有數據渲染的,所以要判斷列表有數據才計算if?(list.length)?{openSpeedReport();} });

    如果引入了 SSR,可認為文檔加載完后,首屏時間結束:

    const?firstScreenCost?=?performance.timing.responseEnd?-?performance.timing.navigationStart;

    6.2 白屏時間

    白屏時間用來表征瀏覽器開始顯示內容的時間。按上節所說,我們用空的 state 渲染了靜態的頁面生成骨架屏。那么可以認為文檔加載下來就結束了白屏時間

    const?cost?=?timing.responseEnd?-?timing.navigationStart;

    那么作為對比,如果沒有接入骨架屏,白屏時間以 performance 的 paint 時間為準

    try?{const?paintTimings?=?performance.getEntriesByType('paint');if?(paintTimings?&&?paintTimings.length)?{const?paintTiming?=?paintTimings[0]?as?PerformanceEntry;return?paintTiming.startTime;} }?catch?(err)?{}

    如果不支持 getEntriesByType 方法的瀏覽器,可以在 JS 開始執行時記錄時間,會有一點偏差,但是偏差很小。

    window.performanceData.jsStartRun?-?timing.navigationStart;

    6.3 交互時間

    6.3.1 主體可交互時間

    對于我們的業務來說,列表是交互的主體,我們更關心的其實是列表可交互的時間。那么這個時間就是列表第一次被渲染后,注冊了事件的時間。這個時間可以認為和沒有引入直出的首屏時間相同,見上方首屏時間。

    我們考慮在引入了 SSR 后,這個時間會變長或變短?雖然文檔相應時間變長,導致 JS 加載時間延后,但是文檔加載后,是帶了初始化數據的,這個數據是會比客戶端發起 CGI 請求取回數據來得快的。因此也就意味著列表的渲染時間提前,主體可交互時間變短。

    6.3.2 整體可交互時間

    在瀏覽器資源加載都完成后,說明達到整體可交互的狀態。

    const?loadEventAndReport?=?()?=>?{const?timing?=?performance.timing;if?(timing.loadEventEnd?>?0)?{const?cost?=?timing.loadEventEnd?-?timing.navigationStart;console.log(cost);} }if?(document.readyState?===?'complete')?{//?為了避免調用測速函數的時候,已經加裝完成,不會有?load?事件loadEventAndReport(); }?else?{//?window的onload事件結束之后?performance?才有?loadEventEnd?數據window.addEventListener('load',?loadEventAndReport); }

    6.4 HTML 請求耗時

    6.4.1 響應耗時

    響應耗時也就是 SSR 渲染的耗時,表示從瀏覽器發起請求開始,到開始接收請求結束。這是我們用來觀察 SSR 性能的重要指標。

    const?htmlResponseCost?=?performance.timing.responseStart?-?performance.timing.requestStart;

    6.4.2 文檔大小

    SSR 因為在文檔里加了渲染后的節點和初始化數據,因此文檔大小會變大。對于文檔大小的變化,那么我們就會關心兩個指標:文檔大小和下載耗時。

    計算文檔大小:

    try?{const?navigationTimings?=?performance.getEntriesByType('navigation');if?(navigationTimings?&&?navigationTimings.length)?{const?navigationTiming?=?navigationTimings[0]?as?PerformanceNavigationTiming;const?size?=?navigationTiming.encodedBodySize;?//?因為開始了?gzip?壓縮,所以關注的是編碼后的大小console.log(`${(size?/?1000).toFixed(2)}KB`);} }?catch?(err)?{}

    6.4.3 下載耗時

    const?htmlDownlaodCost?=?performance.timing.responseEnd?-?performance.timing.responseStart;

    6.5 Node 節點測速

    console.time、console.timeEnd 是我們很經常用來測速某個節點耗時的工具。但是在異步場景下,考察以下的代碼:

    const?calculate?=?()?=>?{console.time('async-time');setTimeout(()?=>?{console.timeEnd('async-time');},?(1?+?Math.random()?*?10)?*?1000); };for?(let?i?=?0;?i?<?3;?i++)?{calculate(); }//?(node:67898)?Warning:?Label?'async-time'?already?exists?for?console.time() //?(node:67898)?Warning:?Label?'async-time'?already?exists?for?console.time() //?async-time:?5894.537ms //?(node:67898)?Warning:?No?such?label?'async-time'?for?console.timeEnd() //?(node:67898)?Warning:?No?such?label?'async-time'?for?console.timeEnd()

    我們考慮 koa 將每個請求都封裝在 ctx 對象上,我們的測速也是基于每個請求下的測速,那么我們可以生成個 ID,對每個請求下的測速都以 ID 來隔離。傳遞到 vm 里的業務代碼,也以封裝好 ID 的函數傳進去。

    const?{?performance?}?=?require('perf_hooks');const?speed?=?new?(class?Speed?{idMarks?=?{};timeAsync?=?(mark,?id)?=>?{if?(!this.idMarks[id])?{this.idMarks[id]?=?{};}this.idMarks[id][mark]?=?performance.now();};timeEndAsync?=?(mark,?id)?=>?{let?cost?=?0;if?(this.idMarks[id]?&&?this.idMarks[id][mark])?{cost?=?performance.now()?-?this.idMarks[id][mark];delete?this.idMarks[id][mark];}console.log(`${mark}:?${cost.toFixed(2)}ms`);return?cost;};timeDestAsync?=?(id)?=>?{delete?this.idMarks[id];}; });const?calculate?=?(id)?=>?{speed.timeAsync('async-time',?id);setTimeout(()?=>?{speed.timeEndAsync('async-time',?id);speed.timeDestAsync(id);},?(1?+?Math.random()?*?10)?*?1000); };for?(let?i?=?0;?i?<?3;?i++)?{calculate(i); }//?async-time:?6131.87ms //?async-time:?6972.74ms //?async-time:?8890.43ms

    7. 部署

    7.1 能否共用?

    當我們做了這么多工作后,尤其是開發環境,運行環境的搭建,我們在想是否可以抽出公共的邏輯,如果有業務有類似的需求的時候,不僅可以針對 SSR 提供基礎功能,還可以具有拓展性,給業務多一個選擇。(也就是抽出了一個叫 tsr 的庫,后面如果提到這個名字就是指的這個)

    實際上我們只需要實現這幾大模塊,以及一些額外的功能就可以了。其余的就可以讓業務拓展。

    7.2 配置化

    我們去除一些細節和重復的,來看一下業務大概的一個配置情況:

    module.exports?=?{mode:?isProduction???'production'?:?'development',?//?標識正式環境還是開發環境port:?80,?//?正式環境下的端口aegisId:?'6602',?//?上報的項目?IDisTestEnv:?!!(process.env.TEST_ENV),?//?如果是運行在?80?端口的正式環境,是否是用來系統測試的renderRoot,?//?渲染的?JS?和?HTML?文件的主目錄preCache:?{?//?因為?html?和?js?文件需要讀取,js?文件還需要預編譯,因此這里列出一些路徑,預讀取和編譯preCacheDir:?['',],preCacheFiles:?[{js:?'mobile-index.js',html:?'mobile-index.html',}],},skeleton:?{?//?生成骨架屏的配置resolveL5:?false,?//生成骨架屏時是否需要解析 L5 //?貓咪寫的代碼:--------55writeToFile:?true,?//?是否寫入文件,路徑是?preCacheDir?加?preCacheFiles},devOptions:?{?//?本地開發路徑staticDir:?path.resolve(__dirname,?'../dist'),?//?本地開發靜態資源的路徑resolveL5:?false,?//?本地是否需要解析?L5,要裝了?IOA?2020?客戶端才可以解析port:?3000,?//?本地開發端口watchDir:?[?//?要額外監聽變動的本地開發路徑path.resolve(__dirname,?'./'),],},middlewares:?{?//?中間件beforeRouter:?[redirect,?//?pc?和移動端互轉的重定向setCookieFromMiniProgram,?//?小程序登錄的中間件],afterRouter:?[],},routers:?{?//?路由,返回?JS?+?HTML?對'/desktop':?pcIndexHandler,'/desktop/m':?mobileIndexHandler,},l5Resolves:?[{?//?l5?配置namespace:?'Production',service:?'969089:65536',?//?tsw?的?l5cgi:?[?//?有用到?l5?的?cgi?路徑'//docs.qq.com/cgi-bin/online_docs/user_info',],}],vmGlobal:?(/*?renderJSFile?*/)?=>?({?//?要注入到?vm?的全局變量window:?undefined,}),hooks:?{?//?一些鉤子函數beforeinjectContent,}, };

    7.3 依賴排除

    前面有提到兩個問題:

  • 全部編譯成一個文件的話,代碼量很大,有十幾萬行,這么大的代碼量都要放到 vm 里跑,意味著有不少代碼是需要重復運行的。但是其實只有某些模塊才有上下文隔離的需求;

  • 被 require 的模塊里訪問全局變量,是 node 上的 globa,并不是 vm 里的上下文;

  • 基于以上兩點,我們在想是否可以將 node_modules 里的模塊排除開,但是一些模塊又有隔離上下文需求的,就一起編譯。這樣可以減少重復代碼的執行,加快執行速度。

    const?nodeExternals?=?require('webpack-node-externals');module.exports?=?{externals:?[nodeExternals({whitelist:?[/@tencent\//,],})], };

    使用 webpack-node-externals 來排除 node_modules 模塊,并且我們自己的模塊不排除。

    但是我們將 vm 的運行環境抽離出單獨的包 tsr,那么業務的 node_moduels 和 tsr 的 node_modules 是隔離的,要想在 tsr 里 require 到業務的 node_modules ,我們需要對 require 的路徑查找做處理。

    require 查找模塊的路徑依賴 module.paths,那么我們只需要將業務 node_modules 的路徑添加到 module.paths 里,就能夠正確找到依賴:

    const?setRequirePaths?=?()?=>?{const?NODE_MODULES?=?'node_modules';let?requirePath?=?renderConfig.renderRoot;?//?這是業務存放代碼的根目錄配置const?paths?=?[path.resolve(requirePath,?NODE_MODULES)];while?(true)?{const?pathInfo?=?path.parse(requirePath);if?(!pathInfo?||?pathInfo.dir?===?'/')?{break;}requirePath?=?pathInfo.dir;paths.unshift(path.resolve(requirePath,?NODE_MODULES));}paths.reverse();module.paths.unshift(...paths); }

    這里有個問題是,假設我們的 Server 有個入口文件是 index.js ,vm 執行的文件是 vm.js ,那么我們在 index.js 文件里運行這個 setRequirePaths 是否有效?

    答案是無效的,因為這兩個文件的 module 對象是不一樣的,我們傳遞到 vm 的全局變量里的 module 是 vm 文件里的。

    同時,為了我們的 React 應用編譯出的代碼能正常 require node_modules 下的模塊,我們還需要對 babel 做更改:

    //?https://stackoverflow.com/questions/57361439/how-to-exclude-core-js-using-usebuiltins-usage/59647913#59647913 const?babelLoader?=?{loader:?'babel-loader',options:?{babelrc:?false,plugins:?['@babel/plugin-transform-runtime',],presets:?['@babel/preset-react',['@babel/preset-env',{modules:?false,useBuiltIns:?'usage',corejs:?3.6,},],],sourceType:?'unambiguous',?//?優先識別?commonjs?模塊}, };

    7.4 云函數 OR 鏡像部署?

    當我們要部署 SSR 服務的時候,可以選擇使用云函數 (SCF)或者鏡像部署(司內習慣用 STKE)(當然是不會選擇傳統的 IDC 機器部署服務了,除了申請機器,安裝各種環境,加機器還要再走一遍流程,然后還要擔心莫名被裁撤)。云函數的概念火一點,但是符合我們的需求嗎?

    當我們后續要做 ABTest 或者是系統環境的分支路徑隔離,就需要同時運行多個分支的代碼,這如果使用云函數的話,有兩個方案:

  • 創建 NFS,并且掛載到云函數里,每次發布更新到 NFS 里,在云函數里做判斷:

  • 創建多個版本的云函數,但是需要在前面再創建個云函數用來判斷請求哪個版本的云函數:

  • 那么我們考慮使用云函數能給我們帶來什么:

  • 彈性伸縮,負載均衡,按需運行

  • 好吧,彈性伸縮我用 STKE 也可以,負載均衡有 L5,STKE 還可以創建負載均衡器。不說 SCF 創建 NFS 還有網絡的要求,在 SCF 里我們仍然需要處理上下文隔離的問題,只會將問題變得更復雜。(原諒我原來先使用的 STKE 的,不過 SCF 也確實去申請平臺子用戶,申請權限,創建到一半了,也確實考察過)

    7.5 基礎鏡像

    選擇了使用鏡像部署的方式來提供服務,那么我們就需要有 docker 鏡像。我們可以提供 tnpm 包,讓業務自己啟動起 tsr 服務。但是提供 docker 基礎鏡像,隱藏啟動的細節,讓業務只設置個配置路徑,是更加合理而有效的方式。

    可以基于 Node:12,設置啟動命令:

    FROM?node:12COPY?./?/tsr/CMD?["node",?"/tsr/scripts/cli.js"]

    但是 node:12,或者 node:12-alpine 鏡像在公司環境下,發起請求到接收請求都要 200-300ms,原因未知,待研究。

    司內環境更推薦使用 tlinux-mini (tlinux 鏡像大),安裝 node,拷貝代碼,并且拷貝啟動腳本到 /etc/kickStart.d 下。(tlinux 為什么不能設置 CMD 啟動命令?因為 tlinux 有自己的初始化進程,進程 pid = 1)啟動后 log 會輸出到 /etc/kickstart.d/startApp.log 。

    FROM?csighub.tencentyun.com/tlinux/tlinux-mini:2.2-stke#?安裝?node RUN?cd?/usr/local/?&&?\wget?https://npm.taobao.org/mirrors/node/v12.13.0/node-v12.13.0-linux-x64.tar.xz?&&?\tar?-xvf?node-v12.13.0-linux-x64.tar.xz?&&?\rm?-rf?node-v12.13.0-linux-x64.tar.xz?&&?\mv?node-v12.13.0-linux-x64/?node?&&?\ln?-s?/usr/local/node/bin/node?/usr/local/bin/node?&&?\ln?-s?/usr/local/node/bin/npm?/usr/local/bin/npm?&&?\ln?-s?/usr/local/node/bin/npx?/usr/local/bin/npxCOPY?./?/tsr/COPY?./scripts/start.sh?./scripts/stop.sh?/etc/kickStart.d/RUN?chmod?+x?/etc/kickStart.d/start.sh?/etc/kickStart.d/stop.sh

    對業務來說只需要依賴 tsr 的鏡像,拷貝一下代碼,設置一下配置路徑就可以了:

    FROM?csighub.tencentyun.com/tsr/tsr:v1.0.38#?編譯的變量,多分支支持 ARG?hookBranchCOPY?./?/tsr-renders/#?為了啟動時同步代碼到?pvc?硬盤的 ENV?TSR_START_SCRIPT?/tsr-renders/start.js#?因為代碼被?start.js?拷貝到?pvc?硬盤,因此配置的路徑在?pvc?硬盤的路徑下 ENV?TSR_CONFIG_PATH?/tsr-renders/renders-pvc/${hookBranch}/config.js

    7.6 開發和調試

    當我們在本地開發的時候,可以用 whistle 來代理請求:

    /^https?:\/\/docs\.qq\.com\/desktop(\/m)?(\/index.html|\/)?(\?.*)?$/?http://127.0.0.1:3000/desktop$1/^https?:\/\/docs\.qq\.com\/desktop(\/m)?(\/(stared|mydoc|trash|folder))(\/.*)?$/?http://127.0.0.1:3000/desktop$1/$3$4/^https?:\/\/docs.qq.com\/desktop\/(m\/)?(.*)\.(.*)/?http://127.0.0.1:3000/$2.$3

    但是開發 Node 應用,修改后頻繁地去重啟會大大降低我們的效率,更不用說我們還有不同倉庫的代碼變更需要監聽,那么我們可以借助 nodemon,但是這里我們有兩個難題:

  • 我們需要 watch 其他倉庫的改動;

  • 我們每次改動之后需要將 tsx 項目編譯成 js 項目;

  • const?path?=?require('path'); const?nodemon?=?require('nodemon'); const?{?renderConfig,?appMain?}?=?require('../constants'); const?logger?=?require('../src/utils/logger');const?isStartedByNodemon?=?!!process.env.NODEMON_PROCESS;const?watches?=?[renderConfig.renderRoot]; watches.push(path.resolve(__dirname,?'../'));watches.push(...(renderConfig.devOptions.watchDir?||?[]));!isStartedByNodemon&&?nodemon({ext:?'js?html?json',watch:?watches,exec:?process.argv.join('?'),runOnChangeOnly:?isStartedByNodemon,delay:?500,env:?{NODEMON_PROCESS:?true,},});nodemon.on('quit',?()?=>?{logger.info('Exit!');process.exit();}).on('restart',?(files)?=>?{if?(files)?{logger.info('Restart!?Change?list:\n',?files);}?else?{logger.info('Start?And?Watching!');}});if?(isStartedByNodemon)?{require(appMain); }?else?{nodemon.emit('restart'); }

    而如果我們要在本地跑起 docker 鏡像呢?

    #!/bin/bashdocker?pull?csighub.tencentyun.com/tsr/tsrdocker?build?-t?desktop-ssr?./tsr-renderscontainer=`docker?run?-d?--privileged?-p?80:80?desktop-ssr`docker?exec?-it?${container}?/bin/shdocker?container?stop?${container}docker?container?rm?${container}

    有兩個點需要注意:

  • 因為依賴的 latest 標簽的鏡像,需要重新 pull,要不然如果本地有,遠程有更新,還是會用本地的;

  • 需要后臺運行后再進入容器,其實就是上面說的 tlinux PID=1 的那個問題;

  • 7.7 CI / CD

    編譯 tsr 我使用的 orange-ci,最主要的就是三步,登錄,編譯,推送。這在本地也可以運行相應的命令跑起來。

    #?正式環境的鏡像?tag,和測試環境不一樣,如?v1.0.3?這樣,倉庫也不一樣 .getProdImageTag:?&getProdImageTag-?name:?獲取正式環境鏡像?Tagscript:?echo?-n?csighub.tencentyun.com/tsr/tsr:$ORANGE_BRANCHenvExport:info:?DOCKER_TIME_TAG#?編譯和推送鏡像 .buildAndPush:?&buildAndPush-?name:?鏡像倉庫登錄script:?docker?login?-u?$CSIGHUB_USER?-p?$CSIGHUB_PWD?csighub.tencentyun.com-?name:?構建鏡像script:?docker?build?--network=host?-t?${DOCKER_TIME_TAG}?-f?dockerfile?./-?name:?推送鏡像script:-?docker?push?${DOCKER_TIME_TAG}

    而如果使用藍盾,最主要的就是構建和推送鏡像和 STKE 操作兩個插件:

    至于一些其他方面的問題,包括:

  • STKE 里怎么解決持久化存儲,怎么同步業務代碼?

  • 怎么處理日志和上報?

  • 怎么不間斷服務更新鏡像?

  • 怎么做就緒檢查和容器健康檢查?

  • 怎么做監控和告警?

  • 這些其實是屬于 STKE 的內容了,可以查找相關的資料看。

    7.8 接入和灰度

    當我們部署了 SSR 的服務后,沒有人會這么虎將原來的 Nginx 服務一次性切到 SSR 的服務吧?我們會先在內部灰度試用,且我們要同步對比兩邊的數據。所以怎么接入就成了我們要考慮的問題。

    7.8.1 路由轉發和機器灰度

    騰訊文檔里有個 tsw 服務用來轉發請求,并且有個 routeproxy 可以設置轉發規則。routeproxy 由兩個參數組成,ID(指定轉發到機器 IP 的規則),FD(指定機器的開發目錄路徑)。

    我們的 SSR 服務能處理的就是列表頁的 PC + 移動端,但是其實像 /desktop/ 目錄下還有其他很多頁面和資源,我們需要將這部分獨立開來。

    在開發階段,我們可以自己寫規則來驗證:

    當我們準備接入了,就需要創建一個新的 L5,新的 L5 的機器仍然是現網的機器,將上訴規則的流量轉到新的 L5。這樣到目前為止,對現網就沒有影響。

    當我們需要在現網上線 SSR 服務的時候,只需要將 SSR 的機器 IP 添加到 L5 里,并逐步調整權重,這樣就能夠按機器來灰度。

    按圖例來說就是這樣的:(當然了,瀏覽器并不會直接和 tsw 交互,前面還有公司的統一接入層)

    7.8.2 多分支灰度

    上面說到在測試環境或者未來的 ABTest,我們需要同時灰度多個分支。以測試環境為例,如果我們要讓 SSR 分支和非 SSR 分支同時工作,除了在一開始部署的時候將代碼拷貝到不同分支的目錄下,如分支為 feature/test,就將代碼拷貝到 /tsr-renders/feature/test 下。在用戶訪問的時候,cookie 是帶有特定的值來標識目前要訪問開發環境下的哪個文件夾的,以很簡單的代碼表示:

    if?(/*?設置了開發分支?*/)?{if?(/*?待渲染的?JS?文件存在?*/)?{//?直出服務}?else?{if?(/*?JS?文件不存在,回退到?SSR?分支,如果?SSR?分支的?JS?文件存在,就用直出?*/)?{//?直出服務}?else?{//?直接輸出?HTML}} }

    (這里其實是為了上線前的驗證,才會回退到 SSR 分支的)

    前面說到我們在編譯的時候會排除 node_modules ,那么在我們做多分支灰度的時候,node_moduels 是如何處理的呢?

    假設我們現在有一個分支,但是我們的某次發布是按 3 個批次來灰度的(實際上我們是按 5 個批次的):

  • 第一批次發布我們需要拷貝 node_modules1 在 Gray1 文件夾,Gray2 和 Gray3 文件夾的用戶訪問到的仍然是舊的版本,里面用的分別是 node_modules2 和 node_modules3 ;

  • 第二批次發布我們需要更新 Gray2,第三批次我們需要更新 Gray3;

  • 這樣會帶來什么問題?這意味著我們第二次,第三次發布的時候,每次都得拷貝 node_moduels 文件夾,假設我們要直接全量,需要同時更新這三個文件夾,就需要拷貝三次 node_modules 。在這個文件夾動輒五六百兆的情況下,即使可以排除開發依賴,在編譯和推送鏡像的時候,時間將會非常長。

    其實我們可以通過軟連接來解決這個問題:

  • 我們第一批次發布的時候,拷貝 node_modules,并且將這個文件夾放在特定于分支的目錄下,拷貝到 pvc 硬盤做持久化存儲;

  • 第二批次發布的時候,將原來 Gray2 文件夾內的 node_modules 通過軟連接指向新分支的 node_moduels,第三批次發布的時候也是一樣的;

  • 需要添加 -l 參數以拷貝軟連接;

  • (()?=>?{if?(isDocker)?{execSync('rsync?-l?-r?-t?-W?--force?/tsr-renders/renders/?/tsr-renders/renders-pvc');} })();

    7.8.3 用戶灰度

    騰訊文檔的用戶灰度機制在于不同的用戶訪問頁面,請求經過 tsw 后,會在 head 帶上用戶屬于哪個灰度批次的值。不同批次訪問的文件夾的代碼是不一樣的。那么我們服務從 head 里取出這個值,就可以從不同的文件夾下取出要運行的渲染 JS 和 HTML。假設只有兩個文件夾 A 和 B,對于某次發布來說:

  • 第一次發布更新文件夾 A,灰度批次為 A 的已經被灰度到,B 批次的仍然保留舊的代碼;

  • 第二次發布更新文件夾 B,所有的用戶訪問的代碼就都是最新的了;

  • 8 后記

    說了這么多,是否還有什么沒說到的?感覺還有幾點:

  • 如何做自動化測試,不僅保障 SSR 代碼不出錯,并且還需要直出的頁面和客戶端差異不大?是用圖片像素比對法,還是 DOM 節點 Diff ?

  • SSR 直出的 DOM 節點是否可以讓 CSR 復用?

  • 是否有更合理的錯誤捕獲方式?

  • 以及 SSR 夠快了嗎?我覺得沒有,它實際上運行耗時都在 40-100ms 以內,React Render 在 20-35ms 左右,CGI 耗時和網絡傳輸才是大頭。像里面嚴重依賴的 doclist CGI 平均耗時 220ms,所以還有優化空間。但是有意義嗎?有,因為這個 CGI 在現網的耗時為 400ms,且還存在并行的 CGI 請求。所以現網的首屏耗時在 1500 - 2200ms 之間。SSR 不僅能夠提升司內環境訪問頁面的首屏速度,更能夠極大提升弱網區域用戶的首屏體驗 。

  • 這些也是我需要繼續研究和實踐的一些點。以兩張對比圖結束文章:

    羅里吧嗦說了很多,當然還有很多細節沒有講到,如果有錯誤的地方歡迎指正。或者有什么好方法好建議也強烈歡迎私聊交流一下。

    我們是在做騰訊文檔的 AlloyTeam 團隊,騰訊文檔大量招人,如果你也想來研究這么有趣的技術,或者加入開放的騰訊大家庭,無論是應聘還是內推,都歡迎聯系 sigmaliu@tencent.com

    總結

    以上是生活随笔為你收集整理的14W 行代码量的前端页面长什么样的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    领导边摸边吃奶边做爽在线观看 | 蜜桃视频插满18在线观看 | 美女毛片一区二区三区四区 | 欧美人与禽zoz0性伦交 | 高潮毛片无遮挡高清免费视频 | 久久久久久av无码免费看大片 | 噜噜噜亚洲色成人网站 | 国产亲子乱弄免费视频 | 久久久www成人免费毛片 | 性色欲情网站iwww九文堂 | 日本xxxx色视频在线观看免费 | 久久伊人色av天堂九九小黄鸭 | 国产av一区二区三区最新精品 | 一本色道久久综合狠狠躁 | 国产免费久久精品国产传媒 | 国产午夜视频在线观看 | 熟女俱乐部五十路六十路av | 成在人线av无码免费 | 国产精品久久久久久无码 | 领导边摸边吃奶边做爽在线观看 | 玩弄人妻少妇500系列视频 | 午夜福利电影 | 精品无码国产一区二区三区av | 狂野欧美激情性xxxx | 97无码免费人妻超级碰碰夜夜 | 亚洲中文字幕成人无码 | 国产 精品 自在自线 | 国产人妻精品午夜福利免费 | 久久国产精品二国产精品 | 奇米综合四色77777久久 东京无码熟妇人妻av在线网址 | 少妇无套内谢久久久久 | 九九久久精品国产免费看小说 | 国产乱人无码伦av在线a | 久久久婷婷五月亚洲97号色 | 无码福利日韩神码福利片 | 国产色视频一区二区三区 | 乱人伦人妻中文字幕无码久久网 | 国产后入清纯学生妹 | 黑人巨大精品欧美一区二区 | 高中生自慰www网站 | 国产黄在线观看免费观看不卡 | 永久免费观看美女裸体的网站 | 国产无av码在线观看 | 无码中文字幕色专区 | 久久人妻内射无码一区三区 | 国产精品.xx视频.xxtv | 久久久国产精品无码免费专区 | 亚洲人交乣女bbw | 日日天日日夜日日摸 | 国产精品国产三级国产专播 | 中文无码伦av中文字幕 | 学生妹亚洲一区二区 | 沈阳熟女露脸对白视频 | 欧美日韩一区二区三区自拍 | 国产精品无套呻吟在线 | 国产成人精品一区二区在线小狼 | 好爽又高潮了毛片免费下载 | 蜜桃视频插满18在线观看 | 婷婷五月综合激情中文字幕 | 成人av无码一区二区三区 | 国产激情精品一区二区三区 | 久久zyz资源站无码中文动漫 | 六十路熟妇乱子伦 | 人妻尝试又大又粗久久 | 狂野欧美性猛交免费视频 | 51国偷自产一区二区三区 | 97久久超碰中文字幕 | 成人无码精品一区二区三区 | 久久国语露脸国产精品电影 | 国产乱人伦偷精品视频 | 欧美日韩色另类综合 | 天天躁夜夜躁狠狠是什么心态 | 97无码免费人妻超级碰碰夜夜 | 久久99精品国产麻豆 | 中文字幕无码av激情不卡 | 国产成人无码av在线影院 | 偷窥日本少妇撒尿chinese | 国产精品久久国产精品99 | 天天av天天av天天透 | 人妻插b视频一区二区三区 | 久久国产自偷自偷免费一区调 | 超碰97人人射妻 | 亚洲aⅴ无码成人网站国产app | 男人和女人高潮免费网站 | 国产乱人伦av在线无码 | 精品日本一区二区三区在线观看 | 亚洲精品久久久久中文第一幕 | 精品国产aⅴ无码一区二区 | 又色又爽又黄的美女裸体网站 | 丰满少妇高潮惨叫视频 | 好爽又高潮了毛片免费下载 | 亚洲精品成人av在线 | 国产人妻人伦精品 | 午夜精品一区二区三区在线观看 | 午夜熟女插插xx免费视频 | 黑人大群体交免费视频 | 人人妻在人人 | 狠狠色噜噜狠狠狠狠7777米奇 | 在线看片无码永久免费视频 | 久久久av男人的天堂 | 一区二区三区乱码在线 | 欧洲 | 极品尤物被啪到呻吟喷水 | 亚洲色偷偷偷综合网 | 国产疯狂伦交大片 | 精品厕所偷拍各类美女tp嘘嘘 | 亚洲欧洲中文日韩av乱码 | 国产亚洲精品精品国产亚洲综合 | 亚洲日韩av一区二区三区四区 | 日韩欧美群交p片內射中文 | 国产精品久久久久久亚洲影视内衣 | 国产人妻精品一区二区三区 | 日本大香伊一区二区三区 | 人人妻人人澡人人爽人人精品 | 国产精品丝袜黑色高跟鞋 | 娇妻被黑人粗大高潮白浆 | 婷婷综合久久中文字幕蜜桃三电影 | 久久99久久99精品中文字幕 | 少妇人妻大乳在线视频 | 无码人妻精品一区二区三区下载 | 亚洲中文字幕成人无码 | 中文字幕人妻无码一夲道 | 国产精品理论片在线观看 | 国产精品久久久久无码av色戒 | 久久五月精品中文字幕 | 国产精品成人av在线观看 | 天天燥日日燥 | 伊在人天堂亚洲香蕉精品区 | 国产午夜福利100集发布 | 亚洲成av人在线观看网址 | 久久国语露脸国产精品电影 | av无码久久久久不卡免费网站 | 久久国产精品偷任你爽任你 | 日本熟妇乱子伦xxxx | 国产麻豆精品一区二区三区v视界 | 77777熟女视频在线观看 а天堂中文在线官网 | 全球成人中文在线 | 国产香蕉尹人综合在线观看 | 性欧美疯狂xxxxbbbb | 国产亚洲日韩欧美另类第八页 | 久久久久99精品国产片 | 丰满人妻被黑人猛烈进入 | 亚洲综合久久一区二区 | аⅴ资源天堂资源库在线 | 中文字幕无线码 | 久久人人97超碰a片精品 | 乱中年女人伦av三区 | 精品一区二区三区无码免费视频 | 久久婷婷五月综合色国产香蕉 | 丰满少妇熟乱xxxxx视频 | 日欧一片内射va在线影院 | 日本乱人伦片中文三区 | 国产成人无码av一区二区 | 成人无码影片精品久久久 | 在线 国产 欧美 亚洲 天堂 | 老熟妇仑乱视频一区二区 | 免费中文字幕日韩欧美 | 日本欧美一区二区三区乱码 | 永久黄网站色视频免费直播 | 国产一区二区三区影院 | 狠狠躁日日躁夜夜躁2020 | 又大又硬又爽免费视频 | 97精品国产97久久久久久免费 | 精品国产一区二区三区四区 | 高潮毛片无遮挡高清免费视频 | 无码人妻丰满熟妇区五十路百度 | 精品国产青草久久久久福利 | 日产精品高潮呻吟av久久 | 十八禁真人啪啪免费网站 | 国产尤物精品视频 | 欧美野外疯狂做受xxxx高潮 | 亚洲精品国产品国语在线观看 | а天堂中文在线官网 | 久久综合色之久久综合 | 色欲人妻aaaaaaa无码 | 国产偷自视频区视频 | 国产97在线 | 亚洲 | 亚洲精品鲁一鲁一区二区三区 | 欧美性黑人极品hd | 免费看少妇作爱视频 | 国产激情一区二区三区 | 精品熟女少妇av免费观看 | 中文字幕无码人妻少妇免费 | 久久国产精品萌白酱免费 | 人妻少妇精品无码专区动漫 | 久久人人爽人人爽人人片av高清 | 国产97在线 | 亚洲 | 99在线 | 亚洲 | 亚洲自偷自偷在线制服 | 日日夜夜撸啊撸 | 中文字幕人妻丝袜二区 | 国产精品成人av在线观看 | aa片在线观看视频在线播放 | 四十如虎的丰满熟妇啪啪 | 日本va欧美va欧美va精品 | 免费国产黄网站在线观看 | 色婷婷香蕉在线一区二区 | 高清无码午夜福利视频 | 77777熟女视频在线观看 а天堂中文在线官网 | 国产农村妇女aaaaa视频 撕开奶罩揉吮奶头视频 | 国产激情精品一区二区三区 | 久久久av男人的天堂 | 久久这里只有精品视频9 | 欧美日韩综合一区二区三区 | 久久亚洲a片com人成 | 中文字幕人成乱码熟女app | 女人色极品影院 | 亚洲国产精华液网站w | 国产在线精品一区二区三区直播 | 精品无码国产一区二区三区av | av在线亚洲欧洲日产一区二区 | 久久成人a毛片免费观看网站 | 亚洲理论电影在线观看 | 日本一卡2卡3卡四卡精品网站 | 欧洲精品码一区二区三区免费看 | 亚洲国精产品一二二线 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 中文字幕精品av一区二区五区 | 宝宝好涨水快流出来免费视频 | 亚洲国产一区二区三区在线观看 | 国产精品内射视频免费 | 国产亚洲精品久久久ai换 | 色综合久久久久综合一本到桃花网 | 欧美野外疯狂做受xxxx高潮 | 人人妻人人澡人人爽欧美一区 | 美女黄网站人色视频免费国产 | 奇米影视7777久久精品 | 日韩人妻少妇一区二区三区 | 丰满妇女强制高潮18xxxx | 欧美性色19p | 蜜桃无码一区二区三区 | 曰韩无码二三区中文字幕 | 一二三四在线观看免费视频 | 久久亚洲a片com人成 | 高潮毛片无遮挡高清免费 | 国产精品.xx视频.xxtv | 久久人人97超碰a片精品 | 丰满人妻翻云覆雨呻吟视频 | 99riav国产精品视频 | 亚洲自偷自偷在线制服 | 全黄性性激高免费视频 | 丰满诱人的人妻3 | 亚洲成a人片在线观看无码3d | 亚洲一区二区三区国产精华液 | 亚洲日韩中文字幕在线播放 | 久久精品国产99久久6动漫 | 国产婷婷色一区二区三区在线 | aa片在线观看视频在线播放 | 国产精品第一区揄拍无码 | 无码一区二区三区在线观看 | 亚洲gv猛男gv无码男同 | 综合网日日天干夜夜久久 | 无码毛片视频一区二区本码 | 蜜桃av抽搐高潮一区二区 | 中国女人内谢69xxxx | 午夜理论片yy44880影院 | 欧美熟妇另类久久久久久不卡 | 国产精品丝袜黑色高跟鞋 | 久久99热只有频精品8 | 亚洲の无码国产の无码步美 | 无遮挡国产高潮视频免费观看 | 一本无码人妻在中文字幕免费 | 午夜理论片yy44880影院 | 亚洲国产精品一区二区第一页 | 老熟女重囗味hdxx69 | 97久久国产亚洲精品超碰热 | 精品一区二区三区无码免费视频 | 夜先锋av资源网站 | 最新版天堂资源中文官网 | 精品国产福利一区二区 | 国内精品人妻无码久久久影院蜜桃 | 99久久人妻精品免费一区 | 亚洲欧洲中文日韩av乱码 | 国产亚洲精品久久久ai换 | 少妇被黑人到高潮喷出白浆 | 亚洲精品无码国产 | 欧美日韩人成综合在线播放 | 亚洲色www成人永久网址 | 乱人伦人妻中文字幕无码 | 国产精品毛片一区二区 | 亚洲成a人片在线观看无码 | 国产口爆吞精在线视频 | 色综合久久久无码中文字幕 | 一本精品99久久精品77 | 免费看男女做好爽好硬视频 | 少妇被黑人到高潮喷出白浆 | 国产99久久精品一区二区 | 特黄特色大片免费播放器图片 | 国产疯狂伦交大片 | 亚洲成a人一区二区三区 | 蜜桃av抽搐高潮一区二区 | 丁香花在线影院观看在线播放 | 欧美日韩亚洲国产精品 | 天天拍夜夜添久久精品 | 无码国模国产在线观看 | 人妻中文无码久热丝袜 | 午夜精品久久久内射近拍高清 | 欧美真人作爱免费视频 | 午夜无码人妻av大片色欲 | 男女性色大片免费网站 | 精品午夜福利在线观看 | 日韩av激情在线观看 | 欧美性猛交xxxx富婆 | 久久综合激激的五月天 | 日日摸天天摸爽爽狠狠97 | 欧美老妇与禽交 | 桃花色综合影院 | 午夜精品久久久久久久久 | 国产人妻久久精品二区三区老狼 | 99久久精品无码一区二区毛片 | 国产精华av午夜在线观看 | 精品国产麻豆免费人成网站 | 骚片av蜜桃精品一区 | 久久天天躁夜夜躁狠狠 | 装睡被陌生人摸出水好爽 | 色 综合 欧美 亚洲 国产 | 久久综合香蕉国产蜜臀av | 国产99久久精品一区二区 | 中文字幕无码免费久久9一区9 | 国精产品一区二区三区 | 成人无码精品1区2区3区免费看 | 国产人成高清在线视频99最全资源 | 女人被男人爽到呻吟的视频 | 又紧又大又爽精品一区二区 | 久久这里只有精品视频9 | 88国产精品欧美一区二区三区 | 亚洲欧美国产精品专区久久 | 国产精品高潮呻吟av久久 | 香蕉久久久久久av成人 | 亚洲日本一区二区三区在线 | 国产亚洲精品久久久闺蜜 | 欧美激情一区二区三区成人 | 无码人妻精品一区二区三区下载 | 亚洲成av人综合在线观看 | 装睡被陌生人摸出水好爽 | 少妇一晚三次一区二区三区 | 久青草影院在线观看国产 | 色综合天天综合狠狠爱 | 国产精品高潮呻吟av久久 | 漂亮人妻洗澡被公强 日日躁 | 久青草影院在线观看国产 | aⅴ亚洲 日韩 色 图网站 播放 | 国产无av码在线观看 | 精品欧美一区二区三区久久久 | 欧美黑人性暴力猛交喷水 | 人妻少妇精品无码专区二区 | 一本大道久久东京热无码av | 亚洲综合无码久久精品综合 | 国产欧美精品一区二区三区 | 捆绑白丝粉色jk震动捧喷白浆 | 国产成人无码a区在线观看视频app | 国产绳艺sm调教室论坛 | 国产免费久久精品国产传媒 | 女人被爽到呻吟gif动态图视看 | 国产在线精品一区二区三区直播 | 人妻尝试又大又粗久久 | 在线播放无码字幕亚洲 | 少妇人妻偷人精品无码视频 | 日本一本二本三区免费 | 精品偷自拍另类在线观看 | 亚洲中文字幕无码中字 | 人妻少妇精品无码专区二区 | 日韩人妻系列无码专区 | 国产精品第一区揄拍无码 | 婷婷五月综合缴情在线视频 | 亚洲色欲久久久综合网东京热 | 亚洲无人区午夜福利码高清完整版 | 白嫩日本少妇做爰 | 丝袜足控一区二区三区 | 人人妻人人澡人人爽精品欧美 | 四虎4hu永久免费 | 欧美日韩色另类综合 | 欧美日本免费一区二区三区 | 亚洲а∨天堂久久精品2021 | 日本xxxx色视频在线观看免费 | 国产乱人偷精品人妻a片 | 最新国产麻豆aⅴ精品无码 | 国产午夜精品一区二区三区嫩草 | 国产午夜无码视频在线观看 | 国产又粗又硬又大爽黄老大爷视 | 日本一卡二卡不卡视频查询 | 内射巨臀欧美在线视频 | 99精品国产综合久久久久五月天 | www国产精品内射老师 | 亚洲高清偷拍一区二区三区 | 玩弄人妻少妇500系列视频 | 日产精品99久久久久久 | 中文字幕av日韩精品一区二区 | 国产精华av午夜在线观看 | 玩弄少妇高潮ⅹxxxyw | 草草网站影院白丝内射 | 日韩在线不卡免费视频一区 | 久久久久成人精品免费播放动漫 | 乌克兰少妇性做爰 | 欧美性生交活xxxxxdddd | 日韩视频 中文字幕 视频一区 | 国精产品一品二品国精品69xx | 性史性农村dvd毛片 | 人妻夜夜爽天天爽三区 | 夜精品a片一区二区三区无码白浆 | 亚洲爆乳大丰满无码专区 | 国产人妻精品一区二区三区 | 永久免费观看国产裸体美女 | 澳门永久av免费网站 | 亚洲精品无码人妻无码 | 国产成人av免费观看 | 人人妻人人澡人人爽人人精品 | 秋霞成人午夜鲁丝一区二区三区 | 曰本女人与公拘交酡免费视频 | 国产精品无码永久免费888 | 日本护士xxxxhd少妇 | 亚洲小说春色综合另类 | 亚洲色无码一区二区三区 | 无码福利日韩神码福利片 | 久久人人爽人人爽人人片ⅴ | 精品国产国产综合精品 | 久久精品国产精品国产精品污 | 亚洲综合精品香蕉久久网 | 熟女体下毛毛黑森林 | 午夜男女很黄的视频 | 亚洲综合无码久久精品综合 | 狠狠色噜噜狠狠狠7777奇米 | 乱中年女人伦av三区 | 国产亚洲tv在线观看 | www成人国产高清内射 | 性做久久久久久久久 | 黄网在线观看免费网站 | 午夜精品久久久久久久 | 中文精品无码中文字幕无码专区 | 亚洲 高清 成人 动漫 | 亚洲精品国产精品乱码视色 | 水蜜桃亚洲一二三四在线 | 日日摸天天摸爽爽狠狠97 | 亚洲国产一区二区三区在线观看 | 精品国产aⅴ无码一区二区 | 亚洲成av人片在线观看无码不卡 | 久久熟妇人妻午夜寂寞影院 | 日本一卡2卡3卡四卡精品网站 | 97精品国产97久久久久久免费 | 国产精品99久久精品爆乳 | 伊人久久大香线焦av综合影院 | 国产麻豆精品精东影业av网站 | 野狼第一精品社区 | 少妇被粗大的猛进出69影院 | 国产av无码专区亚洲a∨毛片 | 国产国语老龄妇女a片 | 国产亚洲精品久久久ai换 | 婷婷五月综合缴情在线视频 | 大肉大捧一进一出视频出来呀 | 人妻夜夜爽天天爽三区 | 三级4级全黄60分钟 | 欧美激情综合亚洲一二区 | 97色伦图片97综合影院 | 亚洲国产高清在线观看视频 | 免费人成网站视频在线观看 | 国产高清不卡无码视频 | 午夜精品一区二区三区在线观看 | 沈阳熟女露脸对白视频 | 天堂亚洲2017在线观看 | 亚洲一区二区三区无码久久 | 久久99精品国产.久久久久 | 亚洲国产欧美日韩精品一区二区三区 | 日韩精品乱码av一区二区 | 久久久久99精品成人片 | 撕开奶罩揉吮奶头视频 | 少妇无码av无码专区在线观看 | 性色欲网站人妻丰满中文久久不卡 | 国产精品久久久久久久9999 | 亚洲第一无码av无码专区 | 欧美zoozzooz性欧美 | 国产亚洲视频中文字幕97精品 | 夜夜高潮次次欢爽av女 | 日本xxxx色视频在线观看免费 | 亚洲成色www久久网站 | 狂野欧美性猛交免费视频 | 日韩欧美成人免费观看 | 97资源共享在线视频 | 在教室伦流澡到高潮hnp视频 | 国产偷国产偷精品高清尤物 | 国产无套粉嫩白浆在线 | 无码免费一区二区三区 | 无码乱肉视频免费大全合集 | 300部国产真实乱 | 亚洲成av人在线观看网址 | 曰韩少妇内射免费播放 | 狠狠色色综合网站 | 人人爽人人爽人人片av亚洲 | 日韩欧美群交p片內射中文 | 久久99精品国产麻豆 | 婷婷丁香五月天综合东京热 | 国产精品久久久久久久9999 | 国产精品99爱免费视频 | 国产精品久久国产三级国 | 亚洲人成影院在线观看 | 97se亚洲精品一区 | 精品成在人线av无码免费看 | 国产人妻精品一区二区三区不卡 | 人人妻人人澡人人爽精品欧美 | 国产性生大片免费观看性 | 国产午夜无码视频在线观看 | 日本一区二区更新不卡 | 国产人妻人伦精品1国产丝袜 | 中文字幕无码热在线视频 | 亚洲成av人片在线观看无码不卡 | 成人精品一区二区三区中文字幕 | 久久久中文字幕日本无吗 | 欧美35页视频在线观看 | 国产精品多人p群无码 | 国产精品内射视频免费 | 日日摸日日碰夜夜爽av | 又黄又爽又色的视频 | 欧美丰满少妇xxxx性 | аⅴ资源天堂资源库在线 | 亚洲aⅴ无码成人网站国产app | 黑森林福利视频导航 | 日日天干夜夜狠狠爱 | 国产明星裸体无码xxxx视频 | 亚洲日本va午夜在线电影 | 国产女主播喷水视频在线观看 | 国产熟妇另类久久久久 | 欧美成人午夜精品久久久 | 丰满少妇弄高潮了www | 久久综合九色综合欧美狠狠 | 午夜精品久久久内射近拍高清 | 99久久精品午夜一区二区 | 少妇性俱乐部纵欲狂欢电影 | 中文字幕乱妇无码av在线 | 内射后入在线观看一区 | 欧美成人高清在线播放 | 国产精品亚洲а∨无码播放麻豆 | 娇妻被黑人粗大高潮白浆 | 亚洲欧美精品aaaaaa片 | 国产人妻大战黑人第1集 | 国产超级va在线观看视频 | 国产精品人妻一区二区三区四 | 亚洲国产欧美日韩精品一区二区三区 | 久久99久久99精品中文字幕 | 久久无码专区国产精品s | 性做久久久久久久久 | 狠狠色噜噜狠狠狠7777奇米 | 久久久久成人片免费观看蜜芽 | 国产成人一区二区三区别 | 无码吃奶揉捏奶头高潮视频 | 丰满人妻被黑人猛烈进入 | 日韩 欧美 动漫 国产 制服 | www国产亚洲精品久久久日本 | 精品无人国产偷自产在线 | 88国产精品欧美一区二区三区 | 2020最新国产自产精品 | 少妇无码一区二区二三区 | 国产一区二区不卡老阿姨 | www国产亚洲精品久久久日本 | 精品国产aⅴ无码一区二区 | 日产国产精品亚洲系列 | 亚洲日韩中文字幕在线播放 | 久久久久se色偷偷亚洲精品av | 午夜熟女插插xx免费视频 | 国产一精品一av一免费 | 日韩欧美中文字幕公布 | 国产一区二区三区影院 | 亚洲精品午夜无码电影网 | 自拍偷自拍亚洲精品被多人伦好爽 | 免费无码的av片在线观看 | 婷婷丁香五月天综合东京热 | 波多野结衣高清一区二区三区 | 国产精品亚洲五月天高清 | 国产亚洲精品久久久久久大师 | 国产精品怡红院永久免费 | 牛和人交xxxx欧美 | 亚洲乱码国产乱码精品精 | 鲁鲁鲁爽爽爽在线视频观看 | 激情国产av做激情国产爱 | 图片区 小说区 区 亚洲五月 | 亚洲精品久久久久中文第一幕 | 亚洲综合伊人久久大杳蕉 | 偷窥日本少妇撒尿chinese | 人妻插b视频一区二区三区 | 乌克兰少妇xxxx做受 | 麻豆国产丝袜白领秘书在线观看 | 黑人巨大精品欧美一区二区 | 少妇无套内谢久久久久 | 婷婷丁香六月激情综合啪 | 亚洲啪av永久无码精品放毛片 | 国产午夜福利亚洲第一 | 亚洲日韩一区二区 | 日韩精品成人一区二区三区 | 亚洲中文无码av永久不收费 | 久久久久久av无码免费看大片 | 欧洲熟妇精品视频 | 7777奇米四色成人眼影 | 国产绳艺sm调教室论坛 | 亚洲欧美精品伊人久久 | 亚洲最大成人网站 | 中文字幕乱码亚洲无线三区 | 永久免费观看美女裸体的网站 | 亚洲 欧美 激情 小说 另类 | 成 人 网 站国产免费观看 | 18精品久久久无码午夜福利 | 国产精品久久久久影院嫩草 | 2020久久香蕉国产线看观看 | 狠狠亚洲超碰狼人久久 | 无套内谢老熟女 | 国内揄拍国内精品少妇国语 | 久久天天躁狠狠躁夜夜免费观看 | 精品无码成人片一区二区98 | 国产成人综合美国十次 | 又大又紧又粉嫩18p少妇 | 人妻少妇精品无码专区动漫 | 永久黄网站色视频免费直播 | 女人高潮内射99精品 | 亚洲日韩一区二区三区 | 国内精品人妻无码久久久影院 | 国产热a欧美热a在线视频 | 少妇的肉体aa片免费 | 曰本女人与公拘交酡免费视频 | 亚洲精品久久久久avwww潮水 | 精品夜夜澡人妻无码av蜜桃 | 日本一区二区更新不卡 | 中文字幕乱码亚洲无线三区 | 国产人妻久久精品二区三区老狼 | 玩弄人妻少妇500系列视频 | 精品熟女少妇av免费观看 | 性欧美疯狂xxxxbbbb | 亚洲另类伦春色综合小说 | 人妻体内射精一区二区三四 | 天天拍夜夜添久久精品 | 日本乱偷人妻中文字幕 | 国产精品自产拍在线观看 | 国产日产欧产精品精品app | 色窝窝无码一区二区三区色欲 | 偷窥村妇洗澡毛毛多 | 成人一区二区免费视频 | 四十如虎的丰满熟妇啪啪 | 国产精品久久精品三级 | 久久久久成人精品免费播放动漫 | 性欧美videos高清精品 | 男女爱爱好爽视频免费看 | 亚洲成av人在线观看网址 | 国产精品无码一区二区三区不卡 | 樱花草在线播放免费中文 | 亚洲精品午夜国产va久久成人 | 欧美猛少妇色xxxxx | 国产精品久久久久9999小说 | 美女毛片一区二区三区四区 | 日韩精品一区二区av在线 | 少妇被黑人到高潮喷出白浆 | 亚洲精品美女久久久久久久 | 精品国产一区av天美传媒 | 国产亚洲视频中文字幕97精品 | 日日噜噜噜噜夜夜爽亚洲精品 | 日日摸日日碰夜夜爽av | 狠狠综合久久久久综合网 | 精品国产一区二区三区av 性色 | 丰满人妻被黑人猛烈进入 | 亚洲小说春色综合另类 | 性欧美牲交在线视频 | 亚洲色无码一区二区三区 | 99久久精品午夜一区二区 | 四虎国产精品一区二区 | 少妇人妻av毛片在线看 | 丰满妇女强制高潮18xxxx | 久精品国产欧美亚洲色aⅴ大片 | 国产精品理论片在线观看 | 精品无码成人片一区二区98 | 中文字幕无线码免费人妻 | 精品亚洲成av人在线观看 | 红桃av一区二区三区在线无码av | 国色天香社区在线视频 | 一本久道久久综合婷婷五月 | 日韩 欧美 动漫 国产 制服 | 久久久久久a亚洲欧洲av冫 | 欧美熟妇另类久久久久久不卡 | 白嫩日本少妇做爰 | 亚洲人成人无码网www国产 | 成人动漫在线观看 | 日韩欧美群交p片內射中文 | 亚洲午夜福利在线观看 | 国产在线精品一区二区高清不卡 | 熟妇人妻无乱码中文字幕 | 九九久久精品国产免费看小说 | 在线 国产 欧美 亚洲 天堂 | 日日摸天天摸爽爽狠狠97 | 性开放的女人aaa片 | 一本加勒比波多野结衣 | 国产区女主播在线观看 | 国产亚洲精品久久久久久久久动漫 | 成人无码精品一区二区三区 | 免费视频欧美无人区码 | 久久熟妇人妻午夜寂寞影院 | 亚洲经典千人经典日产 | 国产卡一卡二卡三 | 精品无码国产自产拍在线观看蜜 | 亚洲日韩一区二区 | 欧美日韩一区二区免费视频 | 国产成人精品视频ⅴa片软件竹菊 | 少妇人妻偷人精品无码视频 | 天天躁夜夜躁狠狠是什么心态 | 国产精品久久久久久久影院 | 亚洲乱码日产精品bd | 蜜臀av在线观看 在线欧美精品一区二区三区 | 日本成熟视频免费视频 | 成人无码精品1区2区3区免费看 | 免费无码av一区二区 | 麻花豆传媒剧国产免费mv在线 | 婷婷色婷婷开心五月四房播播 | 俺去俺来也在线www色官网 | 亚洲精品中文字幕乱码 | 久久99精品国产麻豆 | 樱花草在线社区www | 无码福利日韩神码福利片 | 国产精品18久久久久久麻辣 | 日本欧美一区二区三区乱码 | 国产亚洲精品久久久久久大师 | 欧美日韩一区二区三区自拍 | 3d动漫精品啪啪一区二区中 | 久久久精品人妻久久影视 | 全黄性性激高免费视频 | 亚洲综合色区中文字幕 | 无套内谢的新婚少妇国语播放 | 亚洲中文字幕在线观看 | 成在人线av无码免费 | 久久亚洲国产成人精品性色 | 98国产精品综合一区二区三区 | 高清不卡一区二区三区 | 亚洲午夜久久久影院 | 亚洲色欲色欲欲www在线 | 亚欧洲精品在线视频免费观看 | 国产激情精品一区二区三区 | 性生交大片免费看l | 人人妻人人澡人人爽精品欧美 | 国产亚洲人成a在线v网站 | 国产乱码精品一品二品 | 亚洲国产av精品一区二区蜜芽 | 亚洲男人av香蕉爽爽爽爽 | 天堂久久天堂av色综合 | 亚洲国产成人av在线观看 | 国产精品亚洲五月天高清 | 国产精品无码一区二区三区不卡 | 性啪啪chinese东北女人 | 亚洲中文无码av永久不收费 | 中文精品久久久久人妻不卡 | 国产午夜视频在线观看 | 亚洲精品欧美二区三区中文字幕 | 国产美女极度色诱视频www | 精品无码一区二区三区的天堂 | 成人免费视频视频在线观看 免费 | 少妇高潮喷潮久久久影院 | 性色av无码免费一区二区三区 | 狠狠噜狠狠狠狠丁香五月 | 欧美 日韩 亚洲 在线 | 丰满妇女强制高潮18xxxx | 亚洲精品成a人在线观看 | 国产精品美女久久久久av爽李琼 | 人妻天天爽夜夜爽一区二区 | 九月婷婷人人澡人人添人人爽 | 麻豆国产丝袜白领秘书在线观看 | 少女韩国电视剧在线观看完整 | 国产熟妇另类久久久久 | 精品熟女少妇av免费观看 | 黄网在线观看免费网站 | 99麻豆久久久国产精品免费 | 99国产精品白浆在线观看免费 | 亚洲爆乳大丰满无码专区 | 在线а√天堂中文官网 | 国产av无码专区亚洲a∨毛片 | 亚洲人成网站免费播放 | 久久www免费人成人片 | 正在播放东北夫妻内射 | 四虎4hu永久免费 | 亚洲精品一区二区三区在线 | 亚洲s码欧洲m码国产av | 国产精品亚洲五月天高清 | 国产99久久精品一区二区 | 蜜桃臀无码内射一区二区三区 | 国产成人综合色在线观看网站 | 黄网在线观看免费网站 | aⅴ亚洲 日韩 色 图网站 播放 | 婷婷丁香五月天综合东京热 | 亚洲va欧美va天堂v国产综合 | 少妇无码一区二区二三区 | 日韩无套无码精品 | 中文字幕无码av波多野吉衣 | 欧美自拍另类欧美综合图片区 | 最新国产麻豆aⅴ精品无码 | 精品国偷自产在线视频 | 澳门永久av免费网站 | 大乳丰满人妻中文字幕日本 | 精品人人妻人人澡人人爽人人 | av香港经典三级级 在线 | 国产熟妇另类久久久久 | 一个人看的视频www在线 | 国产精品亚洲五月天高清 | 久久精品人人做人人综合 | 夜夜躁日日躁狠狠久久av | 久久久精品456亚洲影院 | 狠狠躁日日躁夜夜躁2020 | 国产午夜福利亚洲第一 | 国产免费久久久久久无码 | 亚洲色成人中文字幕网站 | 欧美性生交活xxxxxdddd | av在线亚洲欧洲日产一区二区 | 国产精品久久国产精品99 | 少妇的肉体aa片免费 | 正在播放老肥熟妇露脸 | 精品熟女少妇av免费观看 | 亚洲精品国产精品乱码不卡 | 极品嫩模高潮叫床 | 国产亲子乱弄免费视频 | 欧美大屁股xxxxhd黑色 | 亚洲日韩乱码中文无码蜜桃臀网站 | 欧美黑人巨大xxxxx | 老司机亚洲精品影院 | 性啪啪chinese东北女人 | 东京热一精品无码av | 国产做国产爱免费视频 | 一本大道伊人av久久综合 | 国产又粗又硬又大爽黄老大爷视 | 丰满肥臀大屁股熟妇激情视频 | 国产艳妇av在线观看果冻传媒 | 超碰97人人做人人爱少妇 | 97精品人妻一区二区三区香蕉 | 丰满少妇弄高潮了www | 精品 日韩 国产 欧美 视频 | 熟女体下毛毛黑森林 | 大肉大捧一进一出好爽视频 | 日韩视频 中文字幕 视频一区 | 国产亚洲tv在线观看 | 欧美第一黄网免费网站 | 国内精品一区二区三区不卡 | 欧美xxxx黑人又粗又长 | 久久久久人妻一区精品色欧美 | 亚洲欧美色中文字幕在线 | 国产成人无码av片在线观看不卡 | 国产精品毛多多水多 | 亚洲国产精品久久人人爱 | 99麻豆久久久国产精品免费 | 国内精品人妻无码久久久影院 | 日韩精品久久久肉伦网站 | 亚洲成av人综合在线观看 | 亚洲成av人片在线观看无码不卡 | 成人亚洲精品久久久久 | 亚洲精品成a人在线观看 | 午夜无码人妻av大片色欲 | 国产精品久久久久久久影院 | 成在人线av无码免费 | 日本xxxx色视频在线观看免费 | 国产又爽又猛又粗的视频a片 | 久久亚洲日韩精品一区二区三区 | 樱花草在线社区www | 一本久道久久综合狠狠爱 | 风流少妇按摩来高潮 | 精品国产成人一区二区三区 | 久久人人97超碰a片精品 | 久久久久久国产精品无码下载 | 未满小14洗澡无码视频网站 | 天天拍夜夜添久久精品大 | 麻豆精品国产精华精华液好用吗 | 日韩精品无码免费一区二区三区 | 日韩成人一区二区三区在线观看 | 国产精品高潮呻吟av久久 | 国产亚洲美女精品久久久2020 | 国产av久久久久精东av | 国产精品手机免费 | 中文字幕无码av波多野吉衣 | 国产精品资源一区二区 | 亚洲男人av天堂午夜在 | 久久久久久九九精品久 | 亚洲 激情 小说 另类 欧美 | 成人无码精品1区2区3区免费看 | 一本色道久久综合亚洲精品不卡 | 国产精品自产拍在线观看 | 老太婆性杂交欧美肥老太 | 在线a亚洲视频播放在线观看 | 精品国产乱码久久久久乱码 | 国产精品igao视频网 | 国产亚洲美女精品久久久2020 | 丁香啪啪综合成人亚洲 | 精品亚洲韩国一区二区三区 | 色婷婷av一区二区三区之红樱桃 | 成年美女黄网站色大免费全看 | 牛和人交xxxx欧美 | 亚洲成a人片在线观看无码3d | 无码国内精品人妻少妇 | v一区无码内射国产 | 久激情内射婷内射蜜桃人妖 | 2019午夜福利不卡片在线 | 国产日产欧产精品精品app | 日本熟妇人妻xxxxx人hd | 日韩av无码一区二区三区不卡 | 国产精品理论片在线观看 | 亚洲成熟女人毛毛耸耸多 | 国产av一区二区精品久久凹凸 | 国产偷国产偷精品高清尤物 | 亚洲啪av永久无码精品放毛片 | 成熟妇人a片免费看网站 | 日韩在线不卡免费视频一区 | 久久国产劲爆∧v内射 | 日日麻批免费40分钟无码 | 97久久精品无码一区二区 | 一个人看的www免费视频在线观看 | 狠狠色噜噜狠狠狠7777奇米 | 东北女人啪啪对白 | 亚洲狠狠色丁香婷婷综合 | 欧美日本精品一区二区三区 | 少女韩国电视剧在线观看完整 | 国产成人无码av片在线观看不卡 | 国产午夜精品一区二区三区嫩草 | 天堂久久天堂av色综合 | 久久国语露脸国产精品电影 | 亚洲日韩一区二区 | 中文字幕无码人妻少妇免费 | 国产av一区二区三区最新精品 | 少妇厨房愉情理9仑片视频 | 国产精品久久久久无码av色戒 | 中文字幕无码乱人伦 | 国产欧美熟妇另类久久久 | 无码国产激情在线观看 | 国产一区二区不卡老阿姨 | 欧美 丝袜 自拍 制服 另类 | 丰满岳乱妇在线观看中字无码 | 精品无码国产一区二区三区av | 国产无遮挡又黄又爽免费视频 | 麻花豆传媒剧国产免费mv在线 | 国产av无码专区亚洲a∨毛片 | 又粗又大又硬毛片免费看 | 色综合久久中文娱乐网 | 亚洲精品一区二区三区在线观看 | 国产精品自产拍在线观看 | 青青青爽视频在线观看 | 国产精品永久免费视频 | 亚洲精品中文字幕久久久久 | 午夜精品久久久内射近拍高清 | 欧美猛少妇色xxxxx | 精品无人区无码乱码毛片国产 | 欧美日韩在线亚洲综合国产人 | 久久久国产精品无码免费专区 | 美女毛片一区二区三区四区 | 少妇被黑人到高潮喷出白浆 | 两性色午夜视频免费播放 | 欧美兽交xxxx×视频 | 性生交大片免费看女人按摩摩 | 国产精品毛多多水多 | 欧美日韩一区二区免费视频 | 国产精品久久久久无码av色戒 | 国产偷抇久久精品a片69 | 国产在线精品一区二区高清不卡 | 亚洲人亚洲人成电影网站色 | 熟妇人妻无乱码中文字幕 | 少妇的肉体aa片免费 | 日本精品少妇一区二区三区 | 日本肉体xxxx裸交 | 亚洲精品一区二区三区在线 | 蜜桃av蜜臀av色欲av麻 999久久久国产精品消防器材 | 成人无码视频在线观看网站 | 国精产品一品二品国精品69xx | 国模大胆一区二区三区 | 亚洲欧美日韩国产精品一区二区 | 久久综合香蕉国产蜜臀av | 久久人人爽人人爽人人片av高清 | 精品偷拍一区二区三区在线看 | 麻豆人妻少妇精品无码专区 | 国产乱人伦偷精品视频 | 亚洲欧美日韩国产精品一区二区 | 色欲av亚洲一区无码少妇 | 熟妇女人妻丰满少妇中文字幕 | 一本一道久久综合久久 | 国产成人久久精品流白浆 | 国产亚洲人成在线播放 | 最近的中文字幕在线看视频 | 日本精品人妻无码免费大全 | 无码播放一区二区三区 | 亚洲日韩av一区二区三区四区 | 在线播放免费人成毛片乱码 | 无码国内精品人妻少妇 | 精品国产一区二区三区四区 | 久久人人97超碰a片精品 | 国产 浪潮av性色四虎 | 日本肉体xxxx裸交 | 成人免费视频一区二区 | 国产午夜亚洲精品不卡下载 | 性色欲情网站iwww九文堂 | 中文亚洲成a人片在线观看 | 99视频精品全部免费免费观看 | 欧美人与牲动交xxxx | 中文字幕无码免费久久9一区9 | 精品一区二区三区波多野结衣 | 国产精品99久久精品爆乳 | 亚洲热妇无码av在线播放 | 夜夜躁日日躁狠狠久久av | 草草网站影院白丝内射 | 日韩欧美中文字幕公布 | 亚洲人成网站在线播放942 | 色爱情人网站 | 亚洲成av人影院在线观看 | 在线观看国产一区二区三区 | 色偷偷人人澡人人爽人人模 | 国产精品手机免费 | 亚洲日韩av一区二区三区四区 | 少妇久久久久久人妻无码 | 免费国产黄网站在线观看 | 国产精品香蕉在线观看 | 国产内射老熟女aaaa | 日本饥渴人妻欲求不满 | 黑人巨大精品欧美一区二区 | 成人无码精品1区2区3区免费看 | 亚洲最大成人网站 | 日本一区二区更新不卡 | 国产亚洲精品精品国产亚洲综合 | 精品国产aⅴ无码一区二区 | 人妻夜夜爽天天爽三区 | 国产人妻人伦精品 | 久久久www成人免费毛片 | 成人性做爰aaa片免费看不忠 | 国产精品igao视频网 | 国产亚洲精品久久久久久大师 | 成年女人永久免费看片 | 色偷偷av老熟女 久久精品人妻少妇一区二区三区 | 亚洲精品一区三区三区在线观看 | 精品无人国产偷自产在线 | 国产成人一区二区三区在线观看 | 鲁一鲁av2019在线 | 白嫩日本少妇做爰 | 熟女体下毛毛黑森林 | 丰满人妻精品国产99aⅴ | 性色欲情网站iwww九文堂 | 国内少妇偷人精品视频 | 国产乱人偷精品人妻a片 | 日本一卡2卡3卡四卡精品网站 | 亚欧洲精品在线视频免费观看 | 中文字幕中文有码在线 | 美女扒开屁股让男人桶 | 中文字幕无码日韩欧毛 | 大乳丰满人妻中文字幕日本 | 日韩 欧美 动漫 国产 制服 | 久久aⅴ免费观看 | 天天综合网天天综合色 | 亚洲成a人片在线观看无码 | 亚洲成a人一区二区三区 | 日韩欧美中文字幕在线三区 | 在线观看国产午夜福利片 | 国产成人精品优优av | 欧美亚洲日韩国产人成在线播放 | 中文字幕 亚洲精品 第1页 | yw尤物av无码国产在线观看 | 精品无码国产自产拍在线观看蜜 | 国产凸凹视频一区二区 | 国产精品香蕉在线观看 | 国产精品高潮呻吟av久久4虎 | 欧美性猛交xxxx富婆 | 亚洲熟妇自偷自拍另类 | 捆绑白丝粉色jk震动捧喷白浆 | 在教室伦流澡到高潮hnp视频 | 台湾无码一区二区 | 亚洲欧洲中文日韩av乱码 | 日本肉体xxxx裸交 | 亚洲精品一区三区三区在线观看 | 99久久精品国产一区二区蜜芽 | 日韩亚洲欧美中文高清在线 | 国产熟妇另类久久久久 | 亚洲精品中文字幕 | 国内精品久久毛片一区二区 | 亚洲成av人在线观看网址 | 国产精品自产拍在线观看 | 亚洲精品成a人在线观看 | 内射巨臀欧美在线视频 | 5858s亚洲色大成网站www | 无码福利日韩神码福利片 | 高清国产亚洲精品自在久久 | 亚洲 a v无 码免 费 成 人 a v | 国产精品永久免费视频 | 欧美人与禽猛交狂配 | 思思久久99热只有频精品66 | 曰韩少妇内射免费播放 | 日韩无码专区 | 在线观看国产一区二区三区 | 亚洲成a人一区二区三区 | 亚洲国产高清在线观看视频 | 亚洲精品国产精品乱码视色 | 国产精品无套呻吟在线 | 精品熟女少妇av免费观看 | 秋霞成人午夜鲁丝一区二区三区 | 色综合久久久无码中文字幕 | 亚洲人亚洲人成电影网站色 | 久久综合给久久狠狠97色 | 色婷婷综合中文久久一本 | 国产深夜福利视频在线 | 九一九色国产 | 精品偷拍一区二区三区在线看 | 久久久中文字幕日本无吗 | 大屁股大乳丰满人妻 | 无码任你躁久久久久久久 | 中文久久乱码一区二区 | 亚洲天堂2017无码中文 | 色五月五月丁香亚洲综合网 | 国产精品永久免费视频 | 在线视频网站www色 | 精品偷拍一区二区三区在线看 | 国产精品美女久久久网av | 无码播放一区二区三区 | 中文字幕乱码亚洲无线三区 | 国产成人无码区免费内射一片色欲 | 久久亚洲精品成人无码 | 国产精品a成v人在线播放 | 亚洲精品成a人在线观看 | 女人和拘做爰正片视频 | 亚洲最大成人网站 | 国产午夜亚洲精品不卡 | 亚洲 欧美 激情 小说 另类 | 亚洲精品无码人妻无码 | 亚洲国产精品一区二区美利坚 | 日产国产精品亚洲系列 | 两性色午夜视频免费播放 | 东京一本一道一二三区 | 精品夜夜澡人妻无码av蜜桃 | 狠狠色噜噜狠狠狠狠7777米奇 | 在线播放无码字幕亚洲 | 99久久精品日本一区二区免费 | 亚洲精品欧美二区三区中文字幕 | 国产人妻大战黑人第1集 | 国产网红无码精品视频 | 精品欧洲av无码一区二区三区 | 色综合久久88色综合天天 | 色综合久久久久综合一本到桃花网 | 国产精品视频免费播放 | 在线天堂新版最新版在线8 | 久久亚洲a片com人成 | 国产成人精品必看 | 久久久久国色av免费观看性色 | 男女超爽视频免费播放 | 一二三四在线观看免费视频 | 骚片av蜜桃精品一区 | 国产人妻精品一区二区三区不卡 | 亚洲熟熟妇xxxx | 亚洲爆乳大丰满无码专区 | 中文字幕乱码人妻二区三区 | 国产欧美亚洲精品a | 国产在线精品一区二区三区直播 | 女人被男人爽到呻吟的视频 | 色一情一乱一伦一区二区三欧美 | 色五月五月丁香亚洲综合网 | 真人与拘做受免费视频一 | 亚洲熟熟妇xxxx | 中文字幕精品av一区二区五区 | 欧美日本精品一区二区三区 | 亚洲理论电影在线观看 | 亚洲精品一区二区三区在线观看 | 粗大的内捧猛烈进出视频 | 国产精品人妻一区二区三区四 | 精品国产一区二区三区av 性色 | 东北女人啪啪对白 | 亚洲中文字幕成人无码 | 国模大胆一区二区三区 | 99精品久久毛片a片 | 亚洲欧美日韩综合久久久 | 300部国产真实乱 | 亚洲 另类 在线 欧美 制服 | 精品久久综合1区2区3区激情 | 我要看www免费看插插视频 | 无码一区二区三区在线观看 | 97精品人妻一区二区三区香蕉 | 亚洲欧洲中文日韩av乱码 | 国产熟女一区二区三区四区五区 | 国产婷婷色一区二区三区在线 | 中文字幕色婷婷在线视频 | 亚洲精品国偷拍自产在线观看蜜桃 | 久久久成人毛片无码 | 又粗又大又硬又长又爽 | 欧美 日韩 人妻 高清 中文 | 中文字幕无线码免费人妻 | 九九在线中文字幕无码 | 日本一区二区三区免费高清 | √天堂中文官网8在线 | 久久精品国产99精品亚洲 | 毛片内射-百度 | 丝袜美腿亚洲一区二区 | 国产偷国产偷精品高清尤物 | 丝袜 中出 制服 人妻 美腿 | 国产精品-区区久久久狼 | 久久久精品成人免费观看 | 精品厕所偷拍各类美女tp嘘嘘 | 国产精品久免费的黄网站 | 欧美自拍另类欧美综合图片区 | 国产电影无码午夜在线播放 | 久久精品丝袜高跟鞋 | av香港经典三级级 在线 | 国产精品永久免费视频 | 色婷婷综合激情综在线播放 | 亚洲一区二区三区含羞草 | 98国产精品综合一区二区三区 | 免费观看黄网站 | 午夜丰满少妇性开放视频 | 欧美日韩视频无码一区二区三 | 99久久精品日本一区二区免费 | 日韩亚洲欧美中文高清在线 | 欧美 丝袜 自拍 制服 另类 | 人人妻在人人 | 色欲久久久天天天综合网精品 | 麻豆md0077饥渴少妇 | 精品亚洲成av人在线观看 | 亚洲国产日韩a在线播放 | 国产成人无码专区 | 中文字幕无码热在线视频 | 成 人 网 站国产免费观看 | 麻豆果冻传媒2021精品传媒一区下载 | 欧美人与动性行为视频 | 色婷婷欧美在线播放内射 | 欧美成人午夜精品久久久 | 精品厕所偷拍各类美女tp嘘嘘 | 成人毛片一区二区 | 欧美xxxxx精品 | 人妻少妇精品无码专区二区 | 水蜜桃av无码 | 欧美xxxx黑人又粗又长 | 无码av岛国片在线播放 | 蜜臀aⅴ国产精品久久久国产老师 | 国产精品久久久av久久久 | 欧美亚洲日韩国产人成在线播放 | 国产精品久久精品三级 | 动漫av网站免费观看 | 久久人人爽人人爽人人片av高清 | 性开放的女人aaa片 | 久久久久久国产精品无码下载 | 成熟女人特级毛片www免费 | 日韩亚洲欧美中文高清在线 | 国产高清av在线播放 | 色综合视频一区二区三区 | 亚洲小说春色综合另类 | 久久综合狠狠综合久久综合88 | 永久免费精品精品永久-夜色 | 日本饥渴人妻欲求不满 | 亚洲综合另类小说色区 | 一区二区三区乱码在线 | 欧洲 | 日韩少妇内射免费播放 | 2019午夜福利不卡片在线 | 亚洲乱码日产精品bd | 久激情内射婷内射蜜桃人妖 | 夜夜夜高潮夜夜爽夜夜爰爰 | 成熟妇人a片免费看网站 | 3d动漫精品啪啪一区二区中 | www国产精品内射老师 | 国产超级va在线观看视频 | 国产精品亚洲综合色区韩国 | 久久国产自偷自偷免费一区调 | 牲欲强的熟妇农村老妇女视频 | 亚洲精品一区二区三区在线 | 国产偷自视频区视频 | 人人澡人人透人人爽 | 九月婷婷人人澡人人添人人爽 | 2020久久超碰国产精品最新 | 妺妺窝人体色www婷婷 | 黑森林福利视频导航 | 国产精华av午夜在线观看 | 国产午夜福利亚洲第一 | 国产又爽又猛又粗的视频a片 | 日本一区二区更新不卡 | 免费观看激色视频网站 | 玩弄人妻少妇500系列视频 | 国产人妻精品一区二区三区 | 4hu四虎永久在线观看 | 亚洲成av人片天堂网无码】 | 国产精品久久久久久无码 | 狠狠色丁香久久婷婷综合五月 | 亚洲精品一区二区三区在线观看 | 熟妇人妻激情偷爽文 | 噜噜噜亚洲色成人网站 | 欧美怡红院免费全部视频 | 夜夜高潮次次欢爽av女 | 国产区女主播在线观看 | 综合网日日天干夜夜久久 | 夜夜躁日日躁狠狠久久av | 国产精品18久久久久久麻辣 | √天堂资源地址中文在线 | 澳门永久av免费网站 | 内射爽无广熟女亚洲 | 一本大道久久东京热无码av | 狠狠色欧美亚洲狠狠色www | 国产精品久久久久久亚洲毛片 | 欧美老熟妇乱xxxxx | 亚洲 a v无 码免 费 成 人 a v | 欧美激情综合亚洲一二区 | 国产av无码专区亚洲a∨毛片 | 亚洲色成人中文字幕网站 | 亚洲精品一区二区三区大桥未久 | 永久免费观看美女裸体的网站 | 亚洲色成人中文字幕网站 | 成人欧美一区二区三区黑人 | 99久久久无码国产aaa精品 | 秋霞成人午夜鲁丝一区二区三区 | 任你躁在线精品免费 | 九九热爱视频精品 | 国产真实伦对白全集 | 天天做天天爱天天爽综合网 | 国产综合久久久久鬼色 | 日日夜夜撸啊撸 | 中国大陆精品视频xxxx | 日日夜夜撸啊撸 | 国产片av国语在线观看 | 日本又色又爽又黄的a片18禁 | 国产另类ts人妖一区二区 | 亚洲男人av天堂午夜在 | 国精品人妻无码一区二区三区蜜柚 | 国产艳妇av在线观看果冻传媒 | 久久精品女人天堂av免费观看 | 18禁止看的免费污网站 | 美女毛片一区二区三区四区 | 俺去俺来也在线www色官网 | 黄网在线观看免费网站 | 波多野结衣乳巨码无在线观看 | 蜜桃臀无码内射一区二区三区 | 国产偷国产偷精品高清尤物 | 国产美女极度色诱视频www | 国产成人精品一区二区在线小狼 | 亚洲七七久久桃花影院 | 漂亮人妻洗澡被公强 日日躁 | 亚洲精品国产第一综合99久久 | 免费无码午夜福利片69 | 色噜噜亚洲男人的天堂 | 熟妇人妻激情偷爽文 | 久久无码专区国产精品s | 强奷人妻日本中文字幕 | 久久精品无码一区二区三区 | 欧美人与物videos另类 | 亚洲热妇无码av在线播放 | 无码播放一区二区三区 | 内射老妇bbwx0c0ck | 久久99精品久久久久婷婷 | 日韩精品无码一本二本三本色 | 精品国偷自产在线 | a在线观看免费网站大全 | 久久久久久a亚洲欧洲av冫 | 无码人妻久久一区二区三区不卡 | 亚洲中文无码av永久不收费 | 久久婷婷五月综合色国产香蕉 | 天堂在线观看www | 国产精品人人妻人人爽 | 久久精品中文闷骚内射 | 99riav国产精品视频 | 国产麻豆精品一区二区三区v视界 | 国产乱人伦av在线无码 | 日日天干夜夜狠狠爱 | 九一九色国产 | 少妇无码吹潮 | 欧美 日韩 亚洲 在线 | 国产成人无码a区在线观看视频app | 无码中文字幕色专区 | 欧美日韩精品 | 久久久久久亚洲精品a片成人 | 国产精品a成v人在线播放 | 国产人妻精品午夜福利免费 | 久久精品中文闷骚内射 | 国产激情综合五月久久 | 欧美精品免费观看二区 | 国产熟妇另类久久久久 | 欧美性黑人极品hd | 久久久www成人免费毛片 | 性欧美牲交xxxxx视频 | 夜夜夜高潮夜夜爽夜夜爰爰 | 99久久婷婷国产综合精品青草免费 | 日产国产精品亚洲系列 | 国产一区二区三区影院 | 亚洲乱码日产精品bd | 日韩人妻少妇一区二区三区 | 人人妻人人澡人人爽人人精品浪潮 | 3d动漫精品啪啪一区二区中 | 欧美激情内射喷水高潮 | 无码人妻精品一区二区三区下载 | 亚洲成a人片在线观看无码 | 亚洲日韩乱码中文无码蜜桃臀网站 | 樱花草在线社区www | 中文字幕无码人妻少妇免费 | 少妇邻居内射在线 | 免费乱码人妻系列无码专区 | 99久久99久久免费精品蜜桃 | 国产午夜无码精品免费看 | 国产亚av手机在线观看 | 亚洲自偷自偷在线制服 | 欧美成人家庭影院 | 久久久久av无码免费网 | 国产精品久久久一区二区三区 | 欧美色就是色 | 图片区 小说区 区 亚洲五月 | 久久久婷婷五月亚洲97号色 | 亚洲男人av香蕉爽爽爽爽 | 大乳丰满人妻中文字幕日本 | 日本熟妇人妻xxxxx人hd | 国产乱码精品一品二品 | 妺妺窝人体色www在线小说 | 东京热无码av男人的天堂 | 国内精品人妻无码久久久影院 | 中国女人内谢69xxxx | 免费看男女做好爽好硬视频 | 狠狠cao日日穞夜夜穞av | 日日天日日夜日日摸 | 久久久www成人免费毛片 | 亚洲a无码综合a国产av中文 | 日本一区二区三区免费高清 | 日韩av无码一区二区三区不卡 | 国产成人一区二区三区在线观看 | 久久久久久九九精品久 | 国産精品久久久久久久 | 久久国产36精品色熟妇 | 国产婷婷色一区二区三区在线 | 亚洲娇小与黑人巨大交 | 国产人妻大战黑人第1集 | 亚洲国产精华液网站w | 国产艳妇av在线观看果冻传媒 | 一本久久a久久精品亚洲 | 精品乱码久久久久久久 | 97人妻精品一区二区三区 | 性史性农村dvd毛片 | 国产激情无码一区二区 | 国产精品人妻一区二区三区四 | 一本色道婷婷久久欧美 | 久久久久人妻一区精品色欧美 | 国产日产欧产精品精品app | 欧美刺激性大交 | 男女超爽视频免费播放 | 久久精品中文字幕一区 | 亚洲爆乳无码专区 | 全球成人中文在线 | 国产精品永久免费视频 | 亚洲七七久久桃花影院 | 欧美猛少妇色xxxxx | 一本大道伊人av久久综合 | 成人精品视频一区二区三区尤物 | 天堂一区人妻无码 | 亚洲成熟女人毛毛耸耸多 | 无码午夜成人1000部免费视频 | 偷窥村妇洗澡毛毛多 | 日本精品人妻无码免费大全 | 99精品视频在线观看免费 | 国产成人无码午夜视频在线观看 | 国产精品内射视频免费 | 玩弄人妻少妇500系列视频 | 女人被男人躁得好爽免费视频 | 老头边吃奶边弄进去呻吟 | 欧美喷潮久久久xxxxx | 亚洲日韩av一区二区三区中文 | 精品一区二区三区波多野结衣 | 无码午夜成人1000部免费视频 | 午夜福利电影 | 亚洲天堂2017无码中文 | 少妇久久久久久人妻无码 | 亚洲第一网站男人都懂 | 欧美人与牲动交xxxx | 国产精品内射视频免费 | 亚洲日韩av一区二区三区中文 | 国产婷婷色一区二区三区在线 | 国产精品二区一区二区aⅴ污介绍 | 无码av免费一区二区三区试看 | 无码成人精品区在线观看 | 午夜性刺激在线视频免费 | 2019nv天堂香蕉在线观看 | 国内老熟妇对白xxxxhd | 麻豆国产丝袜白领秘书在线观看 | 在线观看免费人成视频 | 久久亚洲a片com人成 | 无码人妻精品一区二区三区下载 | 国产亚av手机在线观看 | 亚洲经典千人经典日产 | 亚洲成av人在线观看网址 | 玩弄人妻少妇500系列视频 | 人妻夜夜爽天天爽三区 | 亚洲欧美精品伊人久久 | 午夜精品一区二区三区在线观看 | 九九久久精品国产免费看小说 | 国产精品人人妻人人爽 | 日韩精品a片一区二区三区妖精 | 天堂无码人妻精品一区二区三区 | 亚拍精品一区二区三区探花 | 国产精品资源一区二区 | 少妇无码av无码专区在线观看 | 国产成人久久精品流白浆 | 一本无码人妻在中文字幕免费 | 在线精品国产一区二区三区 | 成人免费视频视频在线观看 免费 | 日韩精品无码一本二本三本色 | 波多野结衣av一区二区全免费观看 | 精品一区二区不卡无码av | 日本熟妇人妻xxxxx人hd | 中文字幕无线码 | 精品国产国产综合精品 | 欧美丰满熟妇xxxx性ppx人交 | 久久久久亚洲精品中文字幕 | 欧美日韩视频无码一区二区三 | 中文字幕乱码亚洲无线三区 | 在线亚洲高清揄拍自拍一品区 | 成人一在线视频日韩国产 | 中文无码精品a∨在线观看不卡 | 久久久久99精品成人片 | 好屌草这里只有精品 | 成在人线av无码免费 | 十八禁视频网站在线观看 | 国产精品亚洲一区二区三区喷水 | 亚洲中文字幕在线无码一区二区 | 少妇久久久久久人妻无码 | 天天拍夜夜添久久精品 | 日欧一片内射va在线影院 | 一本无码人妻在中文字幕免费 | 国产疯狂伦交大片 | 欧美老妇交乱视频在线观看 | 久久久精品国产sm最大网站 | 亚洲s码欧洲m码国产av | 国产精品久久久av久久久 | 丝袜美腿亚洲一区二区 | 成熟人妻av无码专区 | 丰满人妻一区二区三区免费视频 | 亚洲精品一区二区三区在线 | 亚洲精品鲁一鲁一区二区三区 | 国精产品一品二品国精品69xx | 强奷人妻日本中文字幕 | 国产精品久久福利网站 | 成人欧美一区二区三区 | 国产精品99爱免费视频 | 国产区女主播在线观看 | 性啪啪chinese东北女人 | 久久 国产 尿 小便 嘘嘘 | 欧美日韩一区二区免费视频 | 国产乱码精品一品二品 | 夜精品a片一区二区三区无码白浆 | 亲嘴扒胸摸屁股激烈网站 | 欧美性黑人极品hd | 久久 国产 尿 小便 嘘嘘 | 99久久婷婷国产综合精品青草免费 | 亚洲高清偷拍一区二区三区 | 亚洲娇小与黑人巨大交 | 美女张开腿让人桶 | 丰满人妻翻云覆雨呻吟视频 | 欧美性生交活xxxxxdddd | 中文字幕无码热在线视频 | 国产熟女一区二区三区四区五区 | 双乳奶水饱满少妇呻吟 | 日本xxxx色视频在线观看免费 | 日韩欧美成人免费观看 | 亚洲国产一区二区三区在线观看 | 亚洲中文字幕无码一久久区 | 国产农村乱对白刺激视频 | 久久精品女人的天堂av | 亚洲精品国偷拍自产在线麻豆 | 日本成熟视频免费视频 | 人妻互换免费中文字幕 | 国产精品毛多多水多 | 欧洲极品少妇 | 熟妇人妻中文av无码 | 亚洲精品欧美二区三区中文字幕 | 人妻天天爽夜夜爽一区二区 | 图片区 小说区 区 亚洲五月 | 久久精品人妻少妇一区二区三区 | 久久久久成人精品免费播放动漫 | 1000部啪啪未满十八勿入下载 | 美女毛片一区二区三区四区 | 成人综合网亚洲伊人 | 狂野欧美性猛xxxx乱大交 | 日产国产精品亚洲系列 | 精品偷自拍另类在线观看 | 偷窥日本少妇撒尿chinese | 欧美猛少妇色xxxxx | 亚洲欧洲日本综合aⅴ在线 | 免费国产黄网站在线观看 | 日韩欧美中文字幕公布 | 亚洲高清偷拍一区二区三区 | 亚洲娇小与黑人巨大交 | 国产亚洲精品精品国产亚洲综合 | 久久精品中文闷骚内射 | 性做久久久久久久免费看 | 人人澡人人妻人人爽人人蜜桃 | 强伦人妻一区二区三区视频18 | 精品偷拍一区二区三区在线看 | 精品国产麻豆免费人成网站 | 国产色精品久久人妻 | 国产成人无码av片在线观看不卡 | 亚洲精品国产精品乱码视色 | 狠狠色丁香久久婷婷综合五月 | 在线观看国产午夜福利片 | 午夜无码人妻av大片色欲 | 无码av岛国片在线播放 | 亚洲中文字幕av在天堂 | aⅴ在线视频男人的天堂 | 国产亚洲精品久久久久久 | 一二三四在线观看免费视频 | 亚洲呦女专区 | 国产色xx群视频射精 | 中文字幕 亚洲精品 第1页 | 色婷婷av一区二区三区之红樱桃 | 未满小14洗澡无码视频网站 | 无码精品国产va在线观看dvd | 强辱丰满人妻hd中文字幕 | 国产精品永久免费视频 | 十八禁视频网站在线观看 | 精品国产国产综合精品 | 丁香啪啪综合成人亚洲 | 国产又粗又硬又大爽黄老大爷视 | 4hu四虎永久在线观看 | 国产一区二区三区影院 | 网友自拍区视频精品 | 人妻有码中文字幕在线 | 国产人妻精品午夜福利免费 | 国产成人精品久久亚洲高清不卡 | 在线a亚洲视频播放在线观看 | 中文字幕无码视频专区 | 国产成人无码av片在线观看不卡 | 亚洲熟妇色xxxxx亚洲 | 无码国产激情在线观看 | 巨爆乳无码视频在线观看 | 国产一区二区三区四区五区加勒比 | 国产精华av午夜在线观看 | 黑人巨大精品欧美一区二区 | 久久精品女人天堂av免费观看 | 极品嫩模高潮叫床 | 人妻熟女一区 | 黑人巨大精品欧美黑寡妇 | ass日本丰满熟妇pics | 精品无码国产一区二区三区av | 蜜桃视频韩日免费播放 | 狠狠色丁香久久婷婷综合五月 | 三上悠亚人妻中文字幕在线 | 强开小婷嫩苞又嫩又紧视频 | 国产av无码专区亚洲awww | 国产口爆吞精在线视频 | 麻豆国产97在线 | 欧洲 | 成熟人妻av无码专区 | 亚洲欧美综合区丁香五月小说 | 国产人妻人伦精品1国产丝袜 | 免费网站看v片在线18禁无码 | 精品国产国产综合精品 | 老太婆性杂交欧美肥老太 | 日韩欧美群交p片內射中文 | 久久久久成人精品免费播放动漫 | 精品欧洲av无码一区二区三区 | 亚洲国产高清在线观看视频 | 小泽玛莉亚一区二区视频在线 | 国产精品a成v人在线播放 | 波多野结衣 黑人 | 对白脏话肉麻粗话av | 亚洲精品国产精品乱码不卡 | 图片区 小说区 区 亚洲五月 | 国产人妻久久精品二区三区老狼 | 中文久久乱码一区二区 | 亚洲国精产品一二二线 | 亚洲日韩精品欧美一区二区 | 亚洲国产精品久久久久久 | 国产亚洲精品久久久久久国模美 | 国产亚洲精品久久久久久大师 | 欧美自拍另类欧美综合图片区 | 天天做天天爱天天爽综合网 | 亚洲日本一区二区三区在线 | 欧美老妇交乱视频在线观看 | 伦伦影院午夜理论片 | 激情内射日本一区二区三区 | 国产午夜精品一区二区三区嫩草 | 亚洲国产一区二区三区在线观看 | 国产精品理论片在线观看 | 国产一区二区不卡老阿姨 | 国产无套粉嫩白浆在线 | 国产在热线精品视频 | 午夜无码人妻av大片色欲 | 无码人妻少妇伦在线电影 | 99国产精品白浆在线观看免费 | 无码中文字幕色专区 | 无码国模国产在线观看 | 欧美日韩视频无码一区二区三 | 大肉大捧一进一出视频出来呀 | 强辱丰满人妻hd中文字幕 | 性欧美videos高清精品 | 男人的天堂av网站 | 色偷偷人人澡人人爽人人模 | 国产精品无码mv在线观看 | 色诱久久久久综合网ywww | 在线欧美精品一区二区三区 | 日韩欧美群交p片內射中文 | 伊人色综合久久天天小片 | 永久免费观看美女裸体的网站 | 国产热a欧美热a在线视频 | 国产人妻精品午夜福利免费 | 67194成是人免费无码 | 高潮喷水的毛片 | 久久久无码中文字幕久... |