精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

前端錯誤監控-Sentry自動捕獲前端應用異常原理

開發 前端
當應用代碼中調用 open 方法時,實際使用的是覆寫以后的 open 方法。在新的 open 方法內部,又覆寫了 onreadystatechange,這樣就可以收集到接口請求返回的結果。新的 open 方法內部會使用調用原生的 open 方法。

常見的前端異常及其捕獲方式

前端異常通??梢苑譃橐韵聨追N類型:

  • js 代碼執行時異常;
  • promise 類型異常;
  • 資源加載類型異常;
  • 網絡請求類型異常;
  • 跨域腳本執行異常;
  • 不同類型的異常,捕獲方式不同。

js 代碼執行時異常

js 代碼執行異常,是我們經常遇到異常。這一類型的異常,又可以具體細分為:

  • Error,最基本的錯誤類型,其他的錯誤類型都繼承自該類型。通過 Error,我們可以自定義 Error 類型。
  • RangeError: 范圍錯誤。當出現堆棧溢出(遞歸沒有終止條件)、數值超出范圍(new Array 傳入負數或者一個特別大的整數)情況時會拋出這個異常。
  • ReferenceError,引用錯誤。當一個不存在的對象被引用時發生的異常。
  • SyntaxError,語法錯誤。如變量以數字開頭;花括號沒有閉合等。
  • TypeError,類型錯誤。如把 number 當 str 使用。
  • URIError,向全局 URI 處理函數傳遞一個不合法的 URI 時,就會拋出這個異常。如使用 decodeURI('%')、decodeURIComponent('%')。
  • EvalError, 一個關于 eval 的異常,不會被 javascript 拋出。

具體詳見: Error - JavaScript - MDN Web Docs - Mozilla

通常,我們會通過 try...catch 語句塊來捕獲這一類型異常。如果不使用 try...catch,我們也可以通過 window.onerror = callback 或者 window.addEventListener('error', callback) 的方式進行全局捕獲。

promise 類異常

在使用 promise 時,如果 promise 被 reject 但沒有做 catch 處理時,就會拋出 promise 類異常。

Promise.reject(); // Uncaught (in promise) undefined

promise 類型的異常無法被 try...catch 捕獲,也無法被 window.onerror = callback 或者 window.addEventListener('error', callback) 的方式全局捕獲。針對這一類型的異常, 我們需要通過 window.onrejectionhandled = callback 或者 window.addListener('rejectionhandled', callback) 的方式去全局捕獲。

靜態資源加載類型異常

如果我們頁面的img、js、css 等資源鏈接失效,就會提示資源類型加載如異常。

<img src="localhost:3000/data.png" /> // Get localhost:3000/data.png net::ERR_FILE_NOT_FOUND

針對這一類的異常,我們可以通過 window.addEventListener('error', callback, true) 的方式進行全局捕獲。

這里要注意一點,使用 window.onerror = callback 的方式是無法捕獲靜態資源類異常的。

原因是資源類型錯誤沒有冒泡,只能在捕獲階段捕獲,而 window.onerror 是通過在冒泡階段捕獲錯誤,對靜態資源加載類型異常無效,所以只能借助 window.addEventListener('error', callback, true) 的方式捕獲。

接口請求類型異常

在瀏覽器端發起一個接口請求時,如果請求的 url 的有問題,也會拋出異常。

不同的請求方式,異常捕獲方式也不相同:

  • 接口調用是通過 fetch 發起的

我們可以通過 fetch(url).then(callback).catch(callback) 的方式去捕獲異常。

  • 接口調用通過 xhr 實例發起

如果是 xhr.open 方法執行時出現異常,可以通過 window.addEventListener('error', callback) 或者 window.onerror 的方式捕獲異常。

xhr.open('GET', "https://")  // Uncaught DOMException: Failed to execute 'open' on 'XMLHttpRequest': Invalid URL
at ....

如果是 xhr.send 方法執行時出現異常,可以通過 xhr.onerror 或者 xhr.addEventListener('error', callback) 的方式捕獲異常。

xhr.open('get''/user/userInfo');
xhr.send(); // send localhost:3000/user/userinfo net::ERR_FAILED

跨域腳本執行異常

當項目中引用的第三方腳本執行發生錯誤時,會拋出一類特殊的異常。這類型異常和我們剛才講過的異常都不同,它的 msg 只有 'Script error' 信息,沒有具體的行、列、類型信息。

之以會這樣,是因為瀏覽器的安全機制: 瀏覽器只允許同域下的腳本捕獲具體異常信息,跨域腳本中的異常,不會報告錯誤的細節。

針對這類型的異常,我們可以通過 window.addEventListener('error', callback) 或者 window.onerror 的方式捕獲異常。

如果我們想獲取這類異常的詳情,需要做以下兩個操作:

  • 在發起請求的 script 標簽上添加 crossorigin="anonymous";
  • 請求響應頭中添加 Access-Control-Allow-Origin: *;

這樣就可以獲取到跨域異常的細節信息了。

Sentry 異常監控原理

有效的異常監控需要哪些必備要素

異常監控的核心作用就是通過上報的異常,幫開發人員及時發現線上問題并快速修復。

要達到這個目的,異常監控需要做到以下 3 點:

線上應用出現異常時,可以及時推送給開發人員,安排相關人員去處理。

上報的異常,含有異常類型、發生異常的源文件及行列信息、異常的追蹤棧信息等詳細信息,可以幫助開發人員快速定位問題。

可以獲取發生異常的用戶行為,幫助開發人員、測試人員重現問題和測試回歸。

這三點,分別對應異常自動推送、異常詳情獲取、用戶行為獲取。

異常詳情獲取

為了能自動捕獲應用異常,Sentry 劫持覆寫了 window.onerror 和 window.unhandledrejection 這兩個 api。

劫持覆寫 window.onerror 的代碼如下:

oldErrorHandler = window.onerror;
window.onerror = function (msg, url, line, column, error) {
// 收集異常信息并上報
triggerHandlers('error', {
column: column,
error: error,
line: line,
msg: msg,
url: url,
});
if (oldErrorHandler) {
return oldErrorHandler.apply(this, arguments);
}
return false;
};

劫持覆寫 window.unhandledrejection 的代碼如下:

oldOnUnhandledRejectionHandler = window.onunhandledrejection;
window.onunhandledrejection = function (e) {
// 收集異常信息并上報
triggerHandlers('unhandledrejection', e);
if (oldOnUnhandledRejectionHandler) {
return oldOnUnhandledRejectionHandler.apply(this, arguments);
}
return true;
};

雖然通過劫持覆寫 window.onerror 和 window.unhandledrejection 已足以完成異常自動捕獲,但為了能獲取更詳盡的異常信息, Sentry 在內部做了一些更細微的異常捕獲。

具體來說,就是 Sentry 內部對異常發生的特殊上下文,做了標記。這些特殊上下文包括: dom 節點事件回調、setTimeout / setInterval 回調、xhr 接口調用、requestAnimationFrame 回調等。

舉個 ,如果是 click 事件的 handler 中發生了異常, Sentry 會捕獲這個異常,并將異常發生時的事件 name、dom 節點描述、handler 函數名等信息上報。

具體處理邏輯如下:

  • 標記 setTimeout / setInterval / requestAnimationFrame
  • 為了標記 setTimeout / setInterval / requestAnimationFrame 類型的異常,Sentry 劫持覆寫了原生的 setTimout / setInterval / requestAnimationFrame 方法。新的 setTimeout / setInterval / requestAnimationFrame 方法調用時,會使用 try ... catch 語句塊包裹 callback。

具體實現如下:

var originSetTimeout = window.setTimeout;
window.setTimeout = function() {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
var originalCallback = args[0];
// wrap$1 會對 setTimeout 的入參 callback 使用 try...catch 進行包裝
// 并在 catch 中上報異常
args[0] = wrap$1(originalCallback, {
mechanism: {
data: { function: getFunctionName(original) },
handled: true,
// 異常的上下文是 setTimeout
type: 'setTimeout',
},
});
return original.apply(this, args);
}

  • 當 callback 內部發生異常時,會被 catch 捕獲,捕獲的異常會標記 setTimeout。
  • 由于 setInterval、requestAnimationFrame 的劫持覆寫邏輯和 setTimeout 基本一樣,這里就不再重復說明了,感興趣的小伙伴們可自行實現。
  • 標記 dom 事件 handler
  • 所有的 dom 節點都繼承自 window.Node 對象,dom 對象的 addEventListener 方法來自 Node 的 prototype 對象。
  • 為了標記 dom 事件 handler,Sentry 對 Node.prototype.addEventListener 進行了劫持覆寫。新的 addEventListener 方法調用時,同樣會使用 try ... catch 語句塊包裹傳入的 handler。
  • 相關代碼實現如下:

function xxx() {
var proto = window.Node.prototype;
...
// 覆寫 addEventListener 方法fill(proto, 'addEventListener', function (original) {

return function (eventName, fn, options) {
try {
if (typeof fn.handleEvent === 'function') {
// 使用 try...catch 包括 handle
fn.handleEvent = wrap$1(fn.handleEvent.bind(fn), {
mechanism: {
data: {
function: 'handleEvent',
handler: getFunctionName(fn),
target: target,
},
handled: true,
type: 'instrument',
},
});
}
}
catch (err) {}
return original.apply(this, [
eventName,
wrap$1(fn, {
mechanism: {
data: {
function: 'addEventListener',
handler: getFunctionName(fn),
target: target,
},
handled: true,
type: 'instrument',
},
}),
options,
]);
};
});
}

當 handler 內部發生異常時,會被 catch 捕獲,捕獲的異常會被標記 handleEvent, 并攜帶 event name、event target 等信息。

其實,除了標記 dom 事件回調上下文,Sentry 還可以標記 Notification、WebSocket、XMLHttpRequest 等對象的事件回調上下文。可以這么說,只要一個對象有 addEventListener 方法并且可以被劫持覆寫,那么對應的回調上下文會可以被標記。

標記 xhr 接口回調

為了標記 xhr 接口回調,Sentry 先對 XMLHttpRequest.prototype.send 方法劫持覆寫, 等 xhr 實例使用覆寫以后的 send 方法時,再對 xhr 對象的 onload、onerror、onprogress、onreadystatechange 方法進行了劫持覆寫, 使用 try ... catch 語句塊包裹傳入的 callback。

具體代碼如下:

fill(XMLHttpRequest.prototype, 'send', _wrapXHR);

function _wrapXHR(originalSend) {
return function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
var xhr = this;
var xmlHttpRequestProps = ['onload', 'onerror', 'onprogress', 'onreadystatechange'];
// 劫持覆寫
xmlHttpRequestProps.forEach(function (prop) {
if (prop in xhr && typeof xhr[prop] === 'function') {
// 覆寫
fill(xhr, prop, function (original) {
var wrapOptions = {
mechanism: {
data: {
// 回調觸發的階段
function: prop,
handler: getFunctionName(original),
},
handled: true,
type: 'instrument',
},
};
var originalFunction = getOriginalFunction(original);
if (originalFunction) {
wrapOptions.mechanism.data.handler = getFunctionName(originalFunction);
}
return wrap$1(original, wrapOptions);
});
}
});
return originalSend.apply(this, args);
};

  • 當 callback 內部發生異常時,會被 catch 捕獲,捕獲的異常會被標記對應的請求階段。

有了這些回調上下文信息的幫助,定位異常就更加方便快捷了。

用戶行為獲取

常見的用戶行為,可以歸納為頁面跳轉、鼠標 click 行為、鍵盤 keypress 行為、 fetch / xhr 接口請求、console 打印信息。

Sentry 接入應用以后,會在用戶使用應用的過程中,將上述行為一一收集起來。等到捕獲到異常時,會將收集到的用戶行為和異常信息一起上報。

那 Sentry 是怎么實現收集用戶行為的呢?答案: 劫持覆寫上述操作涉及的 api。

具體實現過程如下:

收集頁面跳轉行為

為了可以收集用戶頁面跳轉行為,Sentry 劫持并覆寫了原生 history 的 pushState、replaceState 方法和 window 的 onpopstate。

劫持覆寫 onpopstate

// 使用 oldPopState 變量保存原生的 onpopstatevar oldPopState = window.onpopstate;
var lastHref;
// 覆寫 onpopstatewindow.onpopstate = function() {
...
var to = window.location.href;
var from = lastHref;
lastHref = to;
// 將頁面跳轉行為收集起來triggerHandlers('history', {
from: from,
to: to,
});
if (oldOnPopState) {
try {
// 使用原生的 popstate return oldOnPopState.apply(this, args);
} catch (e) {
...
}
}
...
}
復制代碼

劫持覆寫 pushState、replaceState

// 保存原生的 pushState 方法
var originPushState = window.history.pushState;
// 保存原生的 replaceState 方法
var originReplaceState = window.history.replaceState;

// 劫持覆寫 pushState
window.history.pushState = function() {
var args = [];
for (var i = 0; i < arguments.length; i++) {
args[i] = arguments[i];
}
var url = args.length > 2 ? args[2] : undefined;
if (url) {
var from = lastHref;
var to = String(url);lastHref = to;
// 將頁面跳轉行為收集起來
triggerHandlers('history', {
from: from,
to: to,
});
}
// 使用原生的 pushState 做頁面跳轉
return originPushState.apply(this, args);
}

// 劫持覆寫 replaceState
window.history.replaceState = function() {
var args = [];
for (var i = 0; i < arguments.length; i++) {
args[i] = arguments[i];
}
var url = args.length > 2 ? args[2] : undefined;
if (url) {
var from = lastHref;
var to = String(url);lastHref = to;
// 將頁面跳轉行為收集起來
triggerHandlers('history', {
from: from,
to: to,
});
}
// 使用原生的 replaceState 做頁面跳轉
return originReplaceState.apply(this, args);
}

收集鼠標 click / 鍵盤 keypress 行為

  • 為了收集用戶鼠標 click 和鍵盤 keypress 行為, Sentry 做了雙保險操作:通過 document 代理 click、keypress 事件來收集 click、keypress 行為;通過劫持 addEventListener 方法來收集 click、keypress 行為;

相關代碼實現如下:

function instrumentDOM() {
...
// triggerDOMHandler 用來收集用戶 click / keypress 行為var triggerDOMHandler = triggerHandlers.bind(null, 'dom');
var globalDOMEventHandler = makeDOMEventHandler(triggerDOMHandler, true);

// 通過 document 代理 click、keypress 事件的方式收集 click、keypress 行為document.addEventListener('click', globalDOMEventHandler, false);
document.addEventListener('keypress', globalDOMEventHandler, false);

['EventTarget', 'Node'].forEach(function (target) {
var proto = window[target] && window[target].prototype;
if (!proto || !proto.hasOwnProperty || !proto.hasOwnProperty('addEventListener')) {
return;
}

// 劫持覆寫 Node.prototype.addEventListener 和 EventTarget.prototype.addEventListenerfill(proto, 'addEventListener', function (originalAddEventListener) {

// 返回新的 addEventListener 覆寫原生的 addEventListenerreturn function (type, listener, options) {

// click、keypress 事件,要做特殊處理,if (type === 'click' || type == 'keypress') {
try {
var el = this;
var handlers_1 = (el.__sentry_instrumentation_handlers__ = el.__sentry_instrumentation_handlers__ || {});
var handlerForType = (handlers_1[type] = handlers_1[type] || { refCount: 0 });
// 如果沒有收集過 click、keypress 行為if (!handlerForType.handler) {
var handler = makeDOMEventHandler(triggerDOMHandler);
handlerForType.handler = handler;
originalAddEventListener.call(this, type, handler, options);
}
handlerForType.refCount += 1;
}
catch (e) {
// Accessing dom properties is always fragile.// Also allows us to skip `addEventListenrs` calls with no proper `this` context.
}
}
// 使用原生的 addEventListener 方法注冊事件return originalAddEventListener.call(this, type, listener, options);
};
});
...
});
}

整個實現過程還是非常巧妙的,很值得拿來細細說明。

首先, Sentry 使用 document 代理了 click、keypress 事件。通過這種方式,用戶的 click、keypress 行為可以被感知,然后被 Sentry 收集。

但這種方式有一個問題,如果應用的 dom 節點是通過 addEventListener 注冊了 click、keypress 事件,并且在事件回調中做了阻止事件冒泡的操作,那么就無法通過代理的方式監控到 click、keypress 事件了。

針對這一種情況, Sentry 采用了覆寫Node.prototype.addEventListener 的方式來監控用戶的 click、keypress 行為。

由于所有的 dom 節點都繼承自 Node 對象,Sentry 劫持覆寫了Node.prototype.addEventListener。當應用代碼通過 addEventListener 訂閱事件時,會使用覆寫以后的 addEventListener 方法。

新的 addEventListener 方法,內部里面也有很巧妙的實現。如果不是 click、keypress 事件,會直接使用原生的 addEventListener 方法注冊應用提供的 listener。但如果是 click、keypress 事件,除了使用原生的 addEventListener 方法注冊應用提供的 listener 外,還使用原生 addEventListener 注冊了一個 handler,這個 handler 執行的時候會將用戶 click、keypress 行為收集起來。

也就是說,如果是 click、keypress 事件,應用程序在調用 addEventListener 的時候,實際上是調用了兩次原生的 addEventListener。

另外,在收集 click、keypress 行為時,Sentry 還會把 target 節點的的父節點信息收集起來,幫助我們快速定位節點位置

收集 fetch / xhr 接口請求行為

同理,為了收集應用的接口請求行為,Sentry 對原生的 fetch 和 xhr 做了劫持覆寫。

劫持覆寫 fetch

var originFetch = window.fetch;

window.fetch = function() {

var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
// 獲取接口 url、method 類型、參數、接口調用時間信息var handlerData = {
args: args,
fetchData: {
method: getFetchMethod(args),
url: getFetchUrl(args),
},
startTimestamp: Date.now(),
};
// 收集接口調用信息triggerHandlers('fetch', __assign({}, handlerData));
return originalFetch.apply(window, args).then(function (response) {
// 接口請求成功,收集返回數據triggerHandlers('fetch', __assign(__assign({}, handlerData), { endTimestamp: Date.now(), response: response }));
return response;
}, function (error) {
// 接口請求失敗,收集接口異常數據triggerHandlers('fetch', __assign(__assign({}, handlerData), { endTimestamp: Date.now(), error: error }));
throw error;
});
}
復制代碼

應用中使用 fetch 發起請求時,實際使用的是新的 fetch 方法。新的 fetch 內部,會使用原生的 fetch 發起請求,并收集接口請求數據和返回結果。

劫持覆寫 xhr

function instrumentXHR() {
...
var xhrproto = XMLHttpRequest.prototype;
// 覆寫 XMLHttpRequest.prototype.openfill(xhrproto, 'open', function (originalOpen) {
return function () {
...
var onreadystatechangeHandler = function () {
if (xhr.readyState === 4) {
...

// 收集接口調用結果triggerHandlers('xhr', {
args: args,
endTimestamp: Date.now(),
startTimestamp: Date.now(),
xhr: xhr,
});
}
};
// 覆寫 onreadystatechangeif ('onreadystatechange' in xhr && typeof xhr.onreadystatechange === 'function') {
fill(xhr, 'onreadystatechange', function (original) {
return function () {
var readyStateArgs = [];
for (var _i = 0; _i < arguments.length; _i++) {
readyStateArgs[_i] = arguments[_i];
}
onreadystatechangeHandler();
return original.apply(xhr, readyStateArgs);
};
});
}
else {
xhr.addEventListener('readystatechange', onreadystatechangeHandler);
}
return originalOpen.apply(xhr, args);
};
});

// 覆寫 XMLHttpRequest.prototype.sendfill(xhrproto, 'send', function (originalSend) {
return function () {
...
// 收集接口調用行為triggerHandlers('xhr', {
args: args,
startTimestamp: Date.now(),
xhr: this,
});
return originalSend.apply(this, args);
};
});
}

復制代碼

Sentry 是通過劫持覆寫 XMLHttpRequest 原型上的 open、send 方法的方式來實現收集接口請求行為的。

當應用代碼中調用 open 方法時,實際使用的是覆寫以后的 open 方法。在新的 open 方法內部,又覆寫了 onreadystatechange,這樣就可以收集到接口請求返回的結果。新的 open 方法內部會使用調用原生的 open 方法。

同樣的,當應用代碼中調用 send 方法時,實際使用的是覆寫以后的 send 方法。新的 send 方法內部先收集接口調用信息,然后調用原生的 send 方法。

收集 console 打印行為

有了前面的鋪墊,console 行為的收集機制理解起來就非常簡單了,實際就是對 console 的 debug、info、warn、error、log、assert 這借個 api 進行劫持覆寫。

代碼如下:

var originConsoleLog = console.log;console.log = function() {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
// 收集 console.log 行為
triggerHandlers('console', { args: args, level: 'log' });
if (originConsoleLog) {
originConsoleLog.apply(console, args);
}
}

文章出自:??前端餐廳ReTech??,如有轉載本文請聯系前端餐廳ReTech今日頭條號。

github:https://github.com/zuopf769

責任編輯:武曉燕 來源: 今日頭條
相關推薦

2022-08-16 10:44:11

Sentry前端異常

2022-11-16 09:03:35

Sentry前端監控

2017-05-04 21:30:32

前端異常監控捕獲方案

2023-08-10 13:46:48

前端資源優化

2023-03-01 09:07:44

前端監控異常

2018-09-14 16:20:37

2015-08-20 10:23:23

前端代碼日志收集

2012-11-05 13:59:12

WebFdSafeJS

2024-11-06 11:15:59

2021-01-19 12:00:39

前端監控代碼

2019-07-15 07:58:10

前端開發技術

2022-06-10 14:09:18

前端監控異常數據

2020-09-04 13:50:35

前端異常監控代碼

2022-03-15 21:38:29

sentry微服務監控

2021-12-15 20:06:48

ReactJSSentry開發者

2021-06-30 19:48:21

前端自動化測試Vue 應用

2020-11-10 09:19:23

Spring BootJava開發

2015-02-03 14:45:55

android全局異常

2021-09-14 23:50:17

Sentry后端監控

2021-06-26 07:40:21

前端自動化測試Jest
點贊
收藏

51CTO技術棧公眾號

欧美日韩成人一区二区三区| 久久精品视频中文字幕| 亚洲国产成人精品无码区99| 欧美一级做性受免费大片免费| 国产综合精品一区| 亚洲国产精品资源| 国产乱子夫妻xx黑人xyx真爽| 日本亚洲一区| 精东粉嫩av免费一区二区三区| 久热在线中文字幕色999舞| 国产精久久久久| 特黄毛片在线观看| 国产农村妇女精品| 亚洲伊人一本大道中文字幕| 五月天婷婷网站| 国产一区二区三区四区| 91精品国产黑色紧身裤美女| 成人性生活视频免费看| 岛国在线视频免费看| 韩国成人精品a∨在线观看| 国a精品视频大全| 亚洲色图100p| 久草在线综合| 7777精品伊人久久久大香线蕉 | 亚洲一区二区三区日韩| 国产原创一区| 亚洲电影第三页| 日韩一区不卡| 六月丁香色婷婷| 日韩黄色免费网站| 欧美国产日产韩国视频| 色噜噜噜噜噜噜| 成人午夜大片| 欧美三电影在线| 男人的天堂狠狠干| 在线毛片网站| 91免费观看在线| 国产精品色视频| 丰满人妻老熟妇伦人精品| 亚洲精品国产成人影院| 在线观看国产精品91| 亚洲高清无码久久| 国产一区二区三区精品在线观看 | 欧美91看片特黄aaaa| 亚洲精品免费在线| 天堂精品视频| 三级理论午夜在线观看| 国产iv一区二区三区| 国产精品一区二区女厕厕| www.国产高清| 欧美影院一区| 最近更新的2019中文字幕| 久久久久久久久免费看无码| 91精品日本| 日韩一区二区免费高清| 国产熟女高潮视频| 草草影院在线| 亚洲成a人片在线不卡一二三区| 亚洲欧洲久久| 91.xxx.高清在线| 亚洲国产精品成人综合色在线婷婷| 国产成人免费观看| 国产成人精品一区二三区四区五区 | 精品国产乱码久久久久久影片| 久久久久久久久久一区二区| 国产成+人+综合+亚洲欧美| 91黄色免费版| 国产精品69页| 深夜视频一区二区| 91福利小视频| 男女视频在线看| 精品视频一区二区三区四区五区| 日本久久一区二区| 亚洲一二三区av| 成人日韩av| 欧美精品亚洲一区二区在线播放| 欧美视频第三页| 欧美片第一页| 欧美日韩在线一区二区| 亚洲欧美自拍另类日韩| 免费成人高清在线视频| 欧美伊人精品成人久久综合97 | 久久资源亚洲| 青青草av免费在线观看| 国产欧美综合色| 亚洲在线视频一区二区| 黄网页在线观看| 亚洲精品乱码久久久久| 9l视频自拍9l视频自拍| 亚洲wwwww| 一二三区精品福利视频| 国产亚洲黄色片| 超碰一区二区| 欧美美女直播网站| 国产欧美一区二| 日韩精品亚洲专区在线观看| 日韩电影第一页| 大胸美女被爆操| 午夜精品久久| 国产91精品久久久久| 337p粉嫩色噜噜噜大肥臀| 狠狠网亚洲精品| 精品国产一区二区三区麻豆免费观看完整版 | 综合网中文字幕| 青娱乐国产在线视频| 欧美精品国产一区| 日本一区二区三区四区视频| 亚洲专区第一页| 成人综合婷婷国产精品久久免费| 欧美xxxx黑人又粗又长精品| 无遮挡动作视频在线观看免费入口| 国产精品久久国产精麻豆99网站 | 天堂在线资源8| 中文字幕在线一区二区三区| 欧美一区二区激情| 成人涩涩视频| 亚洲福利视频免费观看| 四季av中文字幕| 9国产精品视频| 国产日韩中文字幕在线| 天堂网在线播放| 亚洲欧美日韩一区| 精品中文字幕av| 精品中文视频| 一个人www欧美| 欧美三级免费看| 久久亚洲影院| 国产成人亚洲欧美| 男人影院在线观看| 日韩欧美主播在线| 伊人国产精品视频| 深爱激情综合网| 国模叶桐国产精品一区| 亚洲av无码不卡| 99视频一区二区三区| 2021狠狠干| ww久久综合久中文字幕| 日韩精品视频在线播放| 国产一二三四在线| 精品亚洲porn| 精品视频高清无人区区二区三区| 欧美96在线| 欧美色图天堂网| 国产成人无码一区二区在线观看| 欧美成人亚洲| 国产精品丝袜白浆摸在线| 三区在线观看| 偷拍亚洲欧洲综合| 久久性爱视频网站| 欧美激情日韩| 国产日韩在线看片| 免费大片黄在线| 欧美精品自拍偷拍| 国产成人免费观看网站| 三级亚洲高清视频| 麻豆传媒一区| 美女18一级毛片一品久道久久综合| 欧美成人三级在线| 欧美久久久久久久久久久久| 丝袜美腿亚洲综合| 欧洲精品久久| 三级成人黄色影院| 亚洲天堂av综合网| 亚洲第一网站在线观看| 久久久久久99久久久精品网站| 麻豆tv在线播放| 另类在线视频| 午夜精品一区二区三区在线播放| 亚洲奶汁xxxx哺乳期| 亚洲国产美女搞黄色| 亚洲视频天天射| 欧美日本不卡| 国产精品国产精品| 2018av在线| 日韩电影第一页| 日日夜夜狠狠操| 国产精品免费丝袜| 久国产精品视频| 欧美国内亚洲| 国内精品二区| 国产精品一区二区av影院萌芽| 亚洲免费av电影| 中文字幕一二三四| 亚洲免费伊人电影| 97精品人妻一区二区三区蜜桃| 亚洲色诱最新| 性刺激综合网| 婷婷视频一区二区三区| 午夜精品久久久久久久男人的天堂| 黄色电影免费在线看| 制服丝袜中文字幕亚洲| 中国一级免费毛片| 亚洲人成精品久久久久久| 一级做a爰片毛片| 国产一区视频在线看| 黄色一级视频片| 亚洲国产一成人久久精品| 久久精品五月婷婷| 国产精品18| 日韩av手机在线| 美女精品视频| www.久久久久久.com| 日韩美女一级视频| 日韩一二三区视频| 中文有码在线播放| 五月婷婷综合网| 成人涩涩小片视频日本| 久久色在线观看| 精人妻一区二区三区| 麻豆成人久久精品二区三区红 | 精品成人免费一区二区在线播放| 欧美日韩ab片| 黄色在线播放网站| 中文字幕av一区中文字幕天堂| 日本高清视频网站| 日韩亚洲欧美综合| 91激情在线观看| 在线一区二区三区四区五区| 中文字幕在线字幕中文| 亚洲激情五月婷婷| 最新av电影网站| 国产嫩草影院久久久久| 成人精品999| 成人午夜av在线| 九九九久久久久久久| 另类人妖一区二区av| 国产精品wwwww| 一本色道久久精品| 日本a视频在线观看| 国产精品xvideos88| 久久久久久久久影视| 久久久人成影片免费观看| 亚洲精品二区| 成人免费电影网址| 性欧美.com| 国产日韩欧美一区二区三区| 久久精品99| 婷婷亚洲精品| 欧美h视频在线| 国产一区网站| 亚洲 国产 日韩 综合一区| 国产精品嫩草影院在线看| 欧美日韩精品免费看| 天堂av一区二区三区在线播放| 精品无人区一区二区三区 | 一区二区三区欧美在线| 日韩在线观看| 中文字幕99| 亚洲成人99| 青草网在线观看| 黄色日韩在线| 欧美日韩在线一| 免费视频一区| www.日本xxxx| 精品综合免费视频观看| 国产精品久久久久野外| 国产精品18久久久久久久久久久久| 国产黑丝在线视频| 国产91丝袜在线播放0| 日韩精品人妻中文字幕有码| 不卡在线观看av| 亚洲午夜久久久久久久久红桃 | 久青草免费视频| 午夜久久久久久电影| 国产美女激情视频| 欧美伊人精品成人久久综合97 | 日本一级黄色大片| 欧美日韩国产丝袜另类| 无码久久精品国产亚洲av影片| 欧美少妇bbb| 国产不卡av在线播放| 亚洲在线观看av| 亚洲国产成人精品久久| 国产福利电影在线| 欧美成人黑人xx视频免费观看| 欧美性受ⅹ╳╳╳黑人a性爽| 91精品国产自产91精品| 厕沟全景美女厕沟精品| 成人网页在线免费观看| 国产美女撒尿一区二区| 欧美三日本三级少妇三99| 国产精品videosex性欧美| 日韩精品一区在线视频| 日韩精品午夜视频| 国产精品二区视频| 久久精品亚洲精品国产欧美| 成人在线观看免费完整| 欧美日韩国产在线看| 91在线视频国产| 日韩成人在线免费观看| 黄网页免费在线观看| 55夜色66夜色国产精品视频| 久久久免费人体| 国产精品对白刺激久久久| 久久爱www成人| www.亚洲成人网| 免费在线一区观看| 国产十八熟妇av成人一区| 国产精品久久99| 日韩精品在线免费视频| 欧美一区二区美女| 黄色av免费在线看| 久久久久久久久亚洲| 先锋影音网一区二区| 欧美国产视频在线观看| 国产主播一区| 一级黄色片国产| 久久久久久久久久电影| 国产精品第108页| 欧美高清视频在线高清观看mv色露露十八 | 精品国精品自拍自在线| 一本一道波多野毛片中文在线| 91精品国产91久久久久久不卡| 精品视频在线观看网站| 日韩在线三区| 国产农村妇女精品一二区| 免费看91视频| 成人免费在线播放视频| 一区二区三区麻豆| 精品中文视频在线| 毛片网站在线看| 亚洲尤物视频网| 99久久亚洲精品蜜臀| 中文字幕国内自拍| 久久久久综合网| 一区二区三区福利视频| 欧美精品一区二区三区四区| av片在线观看永久免费| 成人黄色片网站| 国产精品91一区二区三区| 国产精品视频黄色| 国产亚洲1区2区3区| 香蕉影院在线观看| 亚洲色图50p| 肉色欧美久久久久久久免费看| 久久精品国产综合精品| 国产日本精品| 亚洲第一黄色网址| 精品福利免费观看| 五月婷婷在线播放| 国内伊人久久久久久网站视频| 999久久久久久久久6666| 精品嫩模一区二区三区| 国产精品18久久久久久vr| 麻豆成人在线视频| 日韩精品一区二区三区老鸭窝 | 欧美亚洲国产成人精品| 欧美美女啪啪| 欧美日韩一区二区在线免费观看| 久久一区二区三区四区| youjizz在线视频| 一本色道久久综合亚洲精品小说| 蜜桃视频成人m3u8| 亚洲图色在线| 精品一区二区三区不卡| 国产97免费视频| 精品免费国产二区三区| 国产资源在线观看入口av| 久久国产手机看片| 日韩二区三区在线观看| 成年人看的免费视频| 777久久久精品| 欧美wwww| 免费久久99精品国产自| 奇米色一区二区| 午夜免费激情视频| 日韩成人av在线| 精品乱码一区二区三区四区| 最新视频 - x88av| 成年人网站91| 男操女视频网站| 欧美大胆a视频| 青青草原在线亚洲| 狠狠躁狠狠躁视频专区| 亚洲免费大片在线观看| 手机看片一区二区三区| 国产精品视频午夜| 欧美激情四色| 亚洲熟妇无码av| 欧美浪妇xxxx高跟鞋交| rebdb初裸写真在线观看| 日本不卡一区| 国产黄色精品网站| 免费的毛片视频| 欧美xxxx14xxxxx性爽| 欧美日韩麻豆| 毛片毛片毛片毛| 精品久久中文字幕久久av| 日韩三级影院| 极品校花啪啪激情久久| 老司机午夜精品99久久| 国产亚洲欧美精品久久久久久| 在线视频日本亚洲性| 北条麻妃在线一区二区免费播放| 91蝌蚪视频在线观看| 亚洲国产精品久久久久婷婷884| av中文在线| 久久精品ww人人做人人爽| 国产尤物一区二区在线| 国产一卡二卡三卡|