طبقة الوصول إلى البيانات الشاملة لتطبيقات الويب.
عادةً على الخادم ، يمكنك الاتصال بأجهزة واجهة برمجة التطبيقات أو قاعدة البيانات مباشرة لجلب بعض البيانات. ومع ذلك ، على العميل ، لا يمكنك دائمًا الاتصال بخدماتك بنفس الطريقة (أي سياسات النطاق المتقاطع). بدلاً من ذلك ، يجب تقديم طلبات XHR/Fetch إلى الخادم الذي يتم إعادة توجيهه إلى خدمتك.
الاضطرار إلى كتابة التعليمات البرمجية بشكل مختلف لكلتا البيئتين هو مكرر وعرضة للخطأ. يوفر FetchR طبقة تجريبية على مكالمات خدمة البيانات الخاصة بك بحيث يمكنك جلب البيانات باستخدام نفس واجهة برمجة التطبيقات على الخادم وجانب العميل.
npm install fetchr --save هام: عندما يكون على المتصفح ، يعتمد Fetchr بالكامل على API Fetch . إذا كنت بحاجة إلى دعم المتصفحات القديمة ، فستحتاج إلى تثبيت polyfill أيضًا (على سبيل المثال. https://github.com/github/fetch).
اتبع الخطوات أدناه لإعداد FetchR بشكل صحيح. هذا يفترض أنك تستخدم إطار عمل Express.
على جانب الخادم ، أضف fetchr الوسيطة إلى تطبيق Express الخاص بك في نقطة نهاية API مخصصة.
تتوقع Fetchr Middleware أنك تستخدم البرامج الوسيطة body-parser (أو الوسيطة البديلة التي تملأ req.body ) قبل استخدام البرامج الوسيطة FetchR.
import express from 'express' ;
import Fetcher from 'fetchr' ;
import bodyParser from 'body-parser' ;
const app = express ( ) ;
// you need to use body-parser middleware before fetcher middleware
app . use ( bodyParser . json ( ) ) ;
app . use ( '/myCustomAPIEndpoint' , Fetcher . middleware ( ) ) ; على جانب العميل ، من الضروري لخيار xhrPath أن يتطابق مع المسار الذي تم تركيب فيه البرامج الوسيطة في الخطوة السابقة
xhrPath هي خاصية تكوين اختيارية تتيح لك تخصيص نقطة النهاية لخدماتك ، وتتخلف عن /api .
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
xhrPath : '/myCustomAPIEndpoint' ,
} ) ; ستحتاج إلى تسجيل أي خدمات بيانات ترغب في استخدامها في التطبيق الخاص بك. ستكون واجهة خدمتك كائنًا يجب تحديد خاصية resource وعملية CRUD واحدة على الأقل. سيتم استخدام خاصية resource عند استدعاء إحدى عمليات CRUD.
// app.js
import Fetcher from 'fetchr' ;
import myDataService from './dataService' ;
Fetcher . registerService ( myDataService ) ; // dataService.js
export default {
// resource is required
resource : 'data_service' ,
// at least one of the CRUD methods is required
read : async function ( { req , resource , params , config } ) {
return { data : 'foo' } ;
} ,
// other methods
// create: async function({ req, resource, params, body, config }) {},
// update: async function({ req, resource, params, body, config }) {},
// delete: async function({ req, resource, params, config }) {}
} ;قد تحتاج خدمات البيانات إلى الوصول إلى كل طلب فردي ، على سبيل المثال ، للحصول على جلسة المستخدم التي تم تسجيلها الحالية. لهذا السبب ، سيتعين إنشاء إنشاء Fetcher مرة واحدة لكل طلب.
على الخزانات ، يتطلب ذلك إنشاء إنشاء فترات لكل طلب ، في الوسيطة الصريحة. على عميل العملاء ، يجب أن يحدث هذا فقط في تحميل الصفحة.
// app.js - server
import express from 'express' ;
import Fetcher from 'fetchr' ;
import myDataService from './dataService' ;
const app = express ( ) ;
// register the service
Fetcher . registerService ( myDataService ) ;
// register the middleware
app . use ( '/myCustomAPIEndpoint' , Fetcher . middleware ( ) ) ;
app . use ( function ( req , res , next ) {
// instantiated fetcher with access to req object
const fetcher = new Fetcher ( {
xhrPath : '/myCustomAPIEndpoint' , // xhrPath will be ignored on the serverside fetcher instantiation
req : req ,
} ) ;
// perform read call to get data
fetcher
. read ( 'data_service' )
. params ( { id : 42 } )
. then ( ( { data , meta } ) => {
// handle data returned from data fetcher in this callback
} )
. catch ( ( err ) => {
// handle error
} ) ;
} ) ; // app.js - client
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
xhrPath : '/myCustomAPIEndpoint' , // xhrPath is REQUIRED on the clientside fetcher instantiation
} ) ;
fetcher
. read ( 'data_api_fetcher' )
. params ( { id : 42 } )
. then ( ( { data , meta } ) => {
// handle data returned from data fetcher in this callback
} )
. catch ( ( err ) => {
// handle errors
} ) ;
// for create you can use the body() method to pass data
fetcher
. create ( 'data_api_create' )
. body ( { some : 'data' } )
. then ( ( { data , meta } ) => {
// handle data returned from data fetcher in this callback
} )
. catch ( ( err ) => {
// handle errors
} ) ; انظر المثال البسيط.
خدمة الخدمة على العميل تصبح بشفافية طلبات جلب. من الجيد تعيين رؤوس ذاكرة التخزين المؤقت على مكالمات الجلب المشتركة. يمكنك القيام بذلك عن طريق توفير معلمة ثالثة في رد الاتصال الخاص بخدمتك. إذا كنت تريد أن تنظر إلى الرؤوس التي تم تعيينها من خلال الخدمة التي اتصلت بها للتو ، فما عليك سوى فحص المعلمة الثالثة في رد الاتصال.
ملاحظة: إذا كنت تستخدم الوعود ، فستكون البيانات الوصفية متاحة على خاصية meta للقيمة التي تم حلها.
// dataService.js
export default {
resource : 'data_service' ,
read : async function ( { req , resource , params , config } ) {
return {
data : 'response' , // business logic
meta : {
headers : {
'cache-control' : 'public, max-age=3600' ,
} ,
statusCode : 200 , // You can even provide a custom statusCode for the fetch response
} ,
} ;
} ,
} ; fetcher
. read ( 'data_service' )
. params ( { id : ### } )
. then ( ( { data , meta } ) {
// data will be 'response'
// meta will have the header and statusCode from above
} ) ; هناك طريقة مريحة تسمى fetcher.getServiceMeta على مثيل FETCHR. ستعيد هذه الطريقة البيانات الوصفية لجميع المكالمات التي حدثت حتى الآن بتنسيق صفيف. في الخادم ، سيتضمن ذلك جميع مكالمات الخدمة للطلب الحالي. في العميل ، سيتضمن ذلك جميع مكالمات الخدمة للجلسة الحالية.
عادةً ما تقوم بتثبيت Fotcher مع بعض الخيارات الافتراضية لجلسة المتصفح بأكملها ، ولكن قد تكون هناك حالات تريد تحديث هذه الخيارات لاحقًا في نفس الجلسة.
يمكنك القيام بذلك باستخدام طريقة updateOptions :
// Start
const fetcher = new Fetcher ( {
xhrPath : '/myCustomAPIEndpoint' ,
xhrTimeout : 2000 ,
} ) ;
// Later, you may want to update the xhrTimeout
fetcher . updateOptions ( {
xhrTimeout : 4000 ,
} ) ; عندما يحدث خطأ في طريقة Fetchr Crud ، يجب عليك رمي كائن خطأ. يجب أن يحتوي كائن الخطأ على statusCode (افتراضي 500) وخاصية output التي تحتوي على كائن قابل للتسلسل JSON والذي سيتم إرساله إلى العميل.
export default {
resource : 'FooService' ,
read : async function create ( req , resource , params , configs ) {
const err = new Error ( 'it failed' ) ;
err . statusCode = 404 ;
err . output = { message : 'Not found' , more : 'meta data' } ;
err . meta = { foo : 'bar' } ;
throw err ;
} ,
} ;وفي خدمتك ، اتصل:
fetcher
. read ( 'someData' )
. params ( { id : '42' } )
. catch ( ( err ) => {
// err instanceof FetchrError -> true
// err.message -> "Not found"
// err.meta -> { foo: 'bar' }
// err.name = 'FetchrError'
// err.output -> { message: "Not found", more: "meta data" }
// err.rawRequest -> { headers: {}, method: 'GET', url: '/api/someData' }
// err.reason -> BAD_HTTP_STATUS | BAD_JSON | TIMEOUT | ABORT | UNKNOWN
// err.statusCode -> 404
// err.timeout -> 3000
// err.url -> '/api/someData'
} ) ; يتم إرجاع كائن ذو طريقة abort عند إنشاء طلبات FETCHR على العميل. هذا مفيد إذا كنت ترغب في إحباط طلب قبل اكتماله.
const req = fetcher
. read ( 'someData' )
. params ( { id : 42 } )
. catch ( ( err ) => {
// err.reason will be ABORT
} ) ;
req . abort ( ) ; xhrTimeout هي خاصية تكوين اختيارية تتيح لك تعيين مهلة (في MS) لجميع طلبات Clientside ، الافتراضية إلى 3000 . على Clientside ، سيتم استخدام XHRPath و XHRTimeout لجميع الطلبات. على الخزانات ، لا يلزم مطلوب XHRPath و XHRTimeout ويتم تجاهلهم.
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
xhrPath : '/myCustomAPIEndpoint' ,
xhrTimeout : 4000 ,
} ) ; إذا كنت ترغب في تعيين مهلة لكل طلب ، فيمكنك الاتصال clientConfig مع خاصية timeout :
fetcher
. read ( 'someData' )
. params ( { id : 42 } )
. clientConfig ( { timeout : 5000 } ) // wait 5 seconds for this request before timing out
. catch ( ( err ) => {
// err.reason will be TIMEOUT
} ) ; بالنسبة لبعض التطبيقات ، قد يكون هناك موقف تحتاج فيه إلى معالجة معاملات الخدمة التي تم تمريرها في الطلب قبل إرسالها إلى الخدمة الفعلية. عادة ، يمكنك معالجتها في الخدمة نفسها. ومع ذلك ، إذا كنت بحاجة إلى إجراء المعالجة عبر العديد من الخدمات (أي تعقيم للأمان) ، فيمكنك استخدام خيار paramsProcessor .
paramsProcessor هي وظيفة يتم تمريرها في طريقة Fetcher.middleware . يتم تمريره ثلاث وسيطات ، وكائن الطلب ، وكائن ServiceInfo ، وكائن Params للخدمة. يمكن لدالة paramsProcessor بعد ذلك تعديل معاملات الخدمة إذا لزم الأمر.
هنا مثال:
/**
Using the app.js from above, you can modify the Fetcher.middleware
method to pass in the paramsProcessor function.
*/
app . use (
'/myCustomAPIEndpoint' ,
Fetcher . middleware ( {
paramsProcessor : function ( req , serviceInfo , params ) {
console . log ( serviceInfo . resource , serviceInfo . operation ) ;
return Object . assign ( { foo : 'fillDefaultValueForFoo' } , params ) ;
} ,
} ) ,
) ; بالنسبة لبعض التطبيقات ، قد يكون هناك موقف تحتاج فيه إلى تعديل الاستجابة قبل نقله إلى العميل. عادة ، يمكنك تطبيق التعديلات الخاصة بك في الخدمة نفسها. ومع ذلك ، إذا كنت بحاجة إلى تعديل الاستجابات عبر العديد من الخدمات (أي إضافة معلومات التصحيح) ، فيمكنك استخدام خيار responseFormatter .
responseFormatter هي وظيفة يتم تمريرها في طريقة Fetcher.middleware . يتم تمرير ثلاث وسيطات ، كائن الطلب ، كائن الاستجابة وكائن استجابة الخدمة (أي البيانات التي تم إرجاعها من خدمتك). يمكن لدالة responseFormatter بعد ذلك تعديل استجابة الخدمة لإضافة معلومات إضافية.
ألق نظرة على المثال أدناه:
/**
Using the app.js from above, you can modify the Fetcher.middleware
method to pass in the responseFormatter function.
*/
app . use (
'/myCustomAPIEndpoint' ,
Fetcher . middleware ( {
responseFormatter : function ( req , res , data ) {
data . debug = 'some debug information' ;
return data ;
} ,
} ) ,
) ; الآن عند إجراء طلب ، سيحتوي ردك على خاصية debug المضافة أعلاه.
يوفر Fetchr دعم CORS من خلال السماح لك بتمرير مضيف الأصل الكامل إلى خيار corsPath .
على سبيل المثال:
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
corsPath : 'http://www.foo.com' ,
xhrPath : '/fooProxy' ,
} ) ;
fetcher . read ( 'service' ) . params ( { foo : 1 } ) . clientConfig ( { cors : true } ) ; بالإضافة إلى ذلك ، يمكنك أيضًا تخصيص كيفية إنشاء عنوان URL GET من خلال تمرير خاصية constructGetUri عند تنفيذ مكالمة read الخاصة بك:
import qs from 'qs' ;
function customConstructGetUri ( uri , resource , params , config ) {
// this refers to the Fetcher object itself that this function is invoked with.
if ( config . cors ) {
return uri + '/' + resource + '?' + qs . stringify ( this . context ) ;
}
// Return `falsy` value will result in `fetcher` using its internal path construction instead.
}
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
corsPath : 'http://www.foo.com' ,
xhrPath : '/fooProxy' ,
} ) ;
fetcher . read ( 'service' ) . params ( { foo : 1 } ) . clientConfig ( {
cors : true ,
constructGetUri : customConstructGetUri ,
} ) ; يمكنك حماية مسارات Fetchr الوسيطة من هجمات CSRF عن طريق إضافة برامج وسيطة أمامه:
app.use('/myCustomAPIEndpoint', csrf(), Fetcher.middleware());
يمكنك استخدام https://github.com/expressjs/csurf لهذا الغرض كمثال.
بعد ذلك ، تحتاج إلى التأكد من إرسال رمز CSRF مع طلباتنا حتى يمكن التحقق منها. للقيام بذلك ، تمرير الرمز المميز في كمفتاح في options.context على العميل:
const fetcher = new Fetcher ( {
xhrPath : '/myCustomAPIEndpoint' , // xhrPath is REQUIRED on the clientside fetcher instantiation
context : {
// These context values are persisted with client calls as query params
_csrf : 'Ax89D94j' ,
} ,
} ) ; سيتم إرسال _csrf هذا في جميع طلبات العميل كمعلمة استعلام بحيث يمكن التحقق منها على الخادم.
عند الاتصال بخدمة Fotcher ، يمكنك تمرير كائن تكوين اختياري.
عند إجراء هذه المكالمة من العميل ، يتم استخدام كائن التكوين لتعيين بعض خيارات الطلب ويمكن استخدامه لتجاوز الخيارات الافتراضية:
//app.js - client
const config = {
timeout : 6000 , // Timeout (in ms) for each request
unsafeAllowRetry : false , // for POST requests, whether to allow retrying this post
} ;
fetcher . read ( 'service' ) . params ( { id : 1 } ) . clientConfig ( config ) ;للحصول على طلبات من الخادم ، يتم تمرير كائن التكوين ببساطة إلى الخدمة التي يتم استدعاؤها.
يمكنك تعيين FECTRR لإعادة إعادة إعادة تلقائي الطلبات عن طريق تحديد تكوين retry في Global أو في تكوين الطلب:
// Globally
const fetchr = new Fetchr ( {
retry : { maxRetries : 2 } ,
} ) ;
// Per request
fetchr . read ( 'service' ) . clientConfig ( {
retry : { maxRetries : 1 } ,
} ) ; من خلال التكوين أعلاه ، ستقوم FetchR بإعادة إعادة محاكمة جميع الطلبات التي تفشل ولكن مرة واحدة فقط عند الاتصال read('service') .
يمكنك مزيد من تخصيص كيفية عمل آلية إعادة المحاولة. هذه كلها إعدادات وقيمها الافتراضية:
const fetchr = new Fetchr ( {
retry : {
maxRetries : 2 , // amount of retries after the first failed request
interval : 200 , // maximum interval between each request in ms (see note below)
statusCodes : [ 0 , 408 ] , // response status code that triggers a retry (see note below)
} ,
unsafeAllowRetry : false , // allow unsafe operations to be retried (see note below)
}فاصلة
يحترم الفاصل الزمني بين كل طلب الصيغة التالية ، استنادًا إلى التراجع الأسي واستراتيجية الارتعاش الكاملة المنشورة في منشور مدونة AWS Architructure:
Math . random ( ) * Math . pow ( 2 , attempt ) * interval ; attempt هي عدد محاولة إعادة المحاولة الحالية بدءًا من 0. interval الافتراضي يتوافق مع 200 مللي ثانية.
statuscodes
لأسباب تاريخية ، تقوم Fetchr بإعادة إعادة 108 ردود فقط وعدم الاستجابات على الإطلاق (على سبيل المثال ، خطأ في الشبكة ، يشار إليه رمز الحالة 0). ومع ذلك ، قد تجد مفيدًا لإعادة محاولة أيضًا على رموز أخرى (502 ، 503 ، 504 يمكن أن يكون مرشحين جيدين لإعادة المحاكاة التلقائية).
unsafeallowretry
بشكل افتراضي ، تقوم Fetchr بإعادة إعادة read الطلبات فقط. يتم ذلك لأسباب تتعلق بالسلامة: قراءة مرتين إدخال من قاعدة بيانات ليست سيئة مثل إنشاء إدخال مرتين. ولكن إذا لم يكن تطبيقك أو موردك بحاجة إلى هذا النوع من الحماية ، فيمكنك السماح بإعادة المحاكاة عن طريق تعيين unsafeAllowRetry إلى true و Fetchr سيحاول إعادة إعادة جميع العمليات.
بشكل افتراضي ، يقوم FECTR بإلغاء جميع قيم السياق إلى عنوان URL للطلب كمعامل استعلام. يتيح لك contextPicker أن يكون لديك عنصر تحكم أكبر على متغيرات السياق التي يتم إرسالها كمعاملات استعلام بناءً على طريقة الطلب ( GET أو POST ). يكون هذا مفيدًا عندما تريد الحد من عدد المتغيرات في GET URL حتى لا يتم تصوير ذاكرة التخزين المؤقت عن طريق الخطأ.
يتبع contextPicker نفس تنسيق المعلمة predicate في lodash/pickBy مع وسيطتين: (value, key) .
const fetcher = new Fetcher ( {
context : {
// These context values are persisted with client calls as query params
_csrf : 'Ax89D94j' ,
device : 'desktop' ,
} ,
contextPicker : {
GET : function ( value , key ) {
// for example, if you don't enable CSRF protection for GET, you are able to ignore it with the url
if ( key === '_csrf' ) {
return false ;
}
return true ;
} ,
// for other method e.g., POST, if you don't define the picker, it will pick the entire context object
} ,
} ) ;
const fetcher = new Fetcher ( {
context : {
// These context values are persisted with client calls as query params
_csrf : 'Ax89D94j' ,
device : 'desktop' ,
} ,
contextPicker : {
GET : [ 'device' ] , // predicate can be an array of strings
} ,
} ) ; عند الاتصال بخدمة Fotcher ، يمكنك إضافة رؤوس طلب مخصصة.
يحتوي الطلب على رؤوس مخصصة عند إضافة خيار headers إلى "ClientConfig".
const config = {
headers : {
'X-VERSION' : '1.0.0' ,
} ,
} ;
fetcher . read ( 'service' ) . params ( { id : 1 } ) . clientConfig ( config ) ; تحتوي جميع الطلبات على رؤوس مخصصة عند إضافة خيار headers إلى وسيطات "Fetcher".
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
headers : {
'X-VERSION' : '1.0.0' ,
} ,
} ) ; لجمع إحصائيات نجاح/فشل/زمن الوصول الخاصة بخدمة Fotcher ، يمكنك تكوين statsCollector لـ Fetchr . سيتم استدعاء وظيفة statsCollector مع حجة واحدة: stats . سيحتوي كائن stats على الحقول التالية:
create|read|update|delete import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
xhrPath : '/myCustomAPIEndpoint' ,
statsCollector : function ( stats ) {
// just console logging as a naive example. there is a lot more you can do here,
// like aggregating stats or filtering out stats you don't want to monitor
console . log (
'Request for resource' ,
stats . resource ,
'with' ,
stats . operation ,
'returned statusCode:' ,
stats . statusCode ,
' within' ,
stats . time ,
'ms' ,
) ;
} ,
} ) ; app . use (
'/myCustomAPIEndpoint' ,
Fetcher . middleware ( {
statsCollector : function ( stats ) {
// just console logging as a naive example. there is a lot more you can do here,
// like aggregating stats or filtering out stats you don't want to monitor
console . log (
'Request for resource' ,
stats . resource ,
'with' ,
stats . operation ,
'returned statusCode:' ,
stats . statusCode ,
' within' ,
stats . time ,
'ms' ,
) ;
} ,
} ) ,
) ; هذا البرنامج مجاني لاستخدامه تحت Yahoo! شركة BSD ترخيص. راجع ملف الترخيص للحصول على نص الترخيص ومعلومات حقوق الطبع والنشر.