Универсальный уровень доступа к данным для веб -приложений.
Как правило, на сервере вы называете свой API или базу данных непосредственно, чтобы получить некоторые данные. Однако на клиенте вы не всегда можете позвонить в свои услуги одинаково (т.е. политики кросс -домена). Вместо этого необходимо сделать запросы XHR/Fetch на сервер, который отправляется в ваш сервис.
Необходимость писать код по -разному для обеих среда является дублирующей и подверженной ошибкам. FetchR предоставляет уровень абстракции по сравнению с вызовами службы данных, чтобы вы могли извлекать данные, используя тот же API на сервере и клиентской стороне.
npm install fetchr --save Важно: когда в браузере Fetchr полностью полагается на API Fetch . Если вам нужно поддерживать старые браузеры, вам также нужно будет установить полифилл (например, https://github.com/github/fetch).
Следуйте приведенным ниже шагам, чтобы правильно настроить FETCHR. Это предполагает, что вы используете структуру Express.
На стороне сервера добавьте промежуточное программное обеспечение Fetchr в ваше приложение Express в пользовательской конечной точке API.
Промежуточное программное обеспечение Fetchr ожидает, что вы используете промежуточное программное обеспечение 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 должен быть создан один раз по запросу.
На сервере это требует, чтобы Fetcher был создан по запросу в Express Middleware. В клиенте это должно произойти только при загрузке страницы.
// 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. Этот метод вернет метаданные для всех вызовов, которые произошли до сих пор в формате массива. На сервере это будет включать в себя все вызовы службы для текущего запроса. В клиенте это будет включать в себя все вызовы службы для текущего сеанса.
Обычно вы создаете экземпляры Fetcher с некоторыми параметрами по умолчанию для всего сеанса браузера, но могут быть случаи, когда вы хотите обновить эти параметры позже в том же сеансе.
Вы можете сделать это с помощью метода 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) для всех запросов клиентов, по умолчанию до 3000 . На клиенте 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 и объект Service 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 будет отправлен во все запросы клиента в качестве параметра запроса, чтобы он мог быть подтвержден на сервере.
При вызове службы Fetcher вы можете передать дополнительный объект конфигурации.
Когда этот вызов сделан из клиента, объект Config используется для установки некоторых параметров запроса и может использоваться для переопределения параметров по умолчанию:
//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 ) ;Для запросов с сервера объект конфигурации просто передается в вызываемую службу.
Вы можете установить FETCHR для автоматической повторной повторной проверки запросов, указав конфигурацию retry в глобальной или в конфигурации запроса:
// 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 Architecture:
Math . random ( ) * Math . pow ( 2 , attempt ) * interval ; attempt - это число текущей попытки повторения, начиная с 0. По interval соответствует 200 мс.
статусные коды
По историческим причинам Fetchr только возвращает 408 ответов и вообще без ответов (например, сетевая ошибка, обозначенная кодом состояния 0). Тем не менее, вы также можете найти полезным, чтобы также повторно повторить другие коды (502, 503, 504 могут быть хорошими кандидатами для автоматических повторений).
несабеллоутробная
По умолчанию FETCHR только повторно запрашивает запросы read . Это делается по соображениям безопасности: дважды чтение записи из базы данных не так плохо, как двойное создание записи. Но если ваше приложение или ресурс не нуждаются в такой защите, вы можете разрешить повторные поиски, установив unsafeAllowRetry true , а Fetchr повторит все операции.
По умолчанию FETCHR добавляет все значения контекста в URL -адрес запроса в виде параметров запросов. contextPicker позволяет вам иметь больший контроль над тем, какие контекстные переменные отправляются в виде параметров запросов в зависимости от метода запроса ( GET или POST ). Это полезно, когда вы хотите ограничить количество переменных в URL -адресе GET , чтобы не случайно кэшировать бюст.
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
} ,
} ) ; При вызове сервиса Fetcher вы можете добавить пользовательские заголовки запросов.
Запрос содержит пользовательские заголовки, когда вы добавляете опцию 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' ,
} ,
} ) ; Чтобы собрать статистику успеха/сбоя/задержки Fetcher Service, вы можете настроить 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! Inc. BSD Лицензия. См. Файл лицензии для текста лицензии и информации об авторском праве.