Capa de acceso de datos universal para aplicaciones web.
Por lo general, en el servidor, llama a su API o base de datos directamente para obtener algunos datos. Sin embargo, en el cliente, no siempre puede llamar a sus servicios de la misma manera (es decir, políticas de dominio cruzado). En cambio, se deben realizar solicitudes XHR/para obtener al servidor que se reenvíe a su servicio.
Tener que escribir código de manera diferente para ambos entornos es duplicada y propensa a errores. Fetchr proporciona una capa de abstracción sobre sus llamadas de servicio de datos para que pueda obtener datos utilizando la misma API en el servidor y el lado del cliente.
npm install fetchr --save IMPORTANTE: Cuando está en el navegador, Fetchr depende completamente de Fetch API. Si necesita admitir navegadores antiguos, también deberá instalar un polyfill (por ejemplo, https://github.com/github/fetch).
Siga los pasos a continuación para configurar fetchr correctamente. Esto supone que está utilizando el marco Express.
En el lado del servidor, agregue el middleware Fetchr a su aplicación Express en un punto final API personalizado.
Fetchr Middleware espera que esté utilizando el middleware body-parser (o un middleware alternativo que poca req.body ) antes de usar el middleware 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 ( ) ) ; En el lado del cliente, es necesario que la opción xhrPath coincida con la ruta donde el middleware estaba montado en el paso anterior
xhrPath es una propiedad de configuración opcional que le permite personalizar el punto final a sus servicios, por defecto a /api .
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
xhrPath : '/myCustomAPIEndpoint' ,
} ) ; Deberá registrar cualquier servicio de datos que desee utilizar en su aplicación. La interfaz para su servicio será un objeto que debe definir una propiedad resource y al menos una operación CRUD. La propiedad resource se utilizará cuando llame a una de las operaciones 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 }) {}
} ;Los servicios de datos pueden necesitar acceso a cada solicitud individual, por ejemplo, para que la sesión actualice la sesión actual. Por esta razón, Fetcher tendrá que ser instanciado una vez por solicitud.
En el servidor, esto requiere que Fetcher sea instanciado por solicitud, en Express Middleware. En el cliente, esto solo debe suceder en la carga de la página.
// 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
} ) ; Vea el ejemplo simple.
Las llamadas al servicio al cliente se convierten en solicitudes de búsqueda de transparencia. Es una buena idea establecer los encabezados de caché en las llamadas comunes de Fetch. Puede hacerlo proporcionando un tercer parámetro en la devolución de llamada de su servicio. Si desea ver qué encabezados establecieron el servicio que acaba de llamar, simplemente inspeccione el tercer parámetro en la devolución de llamada.
Nota: Si está utilizando promesas, los metadatos estarán disponibles en la meta propiedad del valor resuelto.
// 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
} ) ; Hay un método de conveniencia llamado fetcher.getServiceMeta en la instancia de Fetchr. Este método devolverá los metadatos para todas las llamadas que han sucedido hasta ahora en un formato de matriz. En el servidor, esto incluirá todas las llamadas de servicio para la solicitud actual. En el cliente, esto incluirá todas las llamadas de servicio para la sesión actual.
Por lo general, instancia a Fetcher con algunas opciones predeterminadas para toda la sesión del navegador, pero puede haber casos en los que desee actualizar estas opciones más adelante en la misma sesión.
Puede hacerlo con el método updateOptions :
// Start
const fetcher = new Fetcher ( {
xhrPath : '/myCustomAPIEndpoint' ,
xhrTimeout : 2000 ,
} ) ;
// Later, you may want to update the xhrTimeout
fetcher . updateOptions ( {
xhrTimeout : 4000 ,
} ) ; Cuando ocurre un error en su método Fetchr Crud, debe lanzar un objeto de error. El objeto de error debe contener un statusCode (predeterminado 500) y una propiedad output que contiene un objeto JSON Serializable que se enviará al cliente.
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 ;
} ,
} ;Y en su servicio de servicio:
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'
} ) ; Se devuelve un objeto con un método abort al crear solicitudes de Fetchr en el cliente. Esto es útil si desea abortar una solicitud antes de que se complete.
const req = fetcher
. read ( 'someData' )
. params ( { id : 42 } )
. catch ( ( err ) => {
// err.reason will be ABORT
} ) ;
req . abort ( ) ; xhrTimeout es una propiedad de configuración opcional que le permite establecer TimeOut (en MS) para todas las solicitudes junto al cliente, por defecto a 3000 . En el Clientide, XHRPath y XHRTimeOut se utilizarán para todas las solicitudes. En la servidor, XHRPath y XHRTimeOut no son necesarios y se ignoran.
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
xhrPath : '/myCustomAPIEndpoint' ,
xhrTimeout : 4000 ,
} ) ; Si desea establecer un tiempo de espera por solicitud, puede llamar clientConfig con una propiedad 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
} ) ; Para algunas solicitudes, puede haber una situación en la que necesite procesar los parámetros del servicio aprobados en la solicitud antes de que se envíen al servicio real. Por lo general, los procesaría en el servicio en sí. Sin embargo, si necesita realizar el procesamiento en muchos servicios (es decir, desinfectación para la seguridad), puede usar la opción paramsProcessor .
paramsProcessor es una función que se pasa al método Fetcher.middleware . Se pasa tres argumentos, el objeto de solicitud, el objeto ServiceInfo y el objeto de parámetros de servicio. La función paramsProcessor puede modificar los parámetros de servicio si es necesario.
Aquí hay un ejemplo:
/**
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 ) ;
} ,
} ) ,
) ; Para algunas aplicaciones, puede haber una situación en la que necesite modificar la respuesta antes de pasarlo al cliente. Por lo general, aplicaría sus modificaciones en el servicio en sí. Sin embargo, si necesita modificar las respuestas en muchos servicios (es decir, agregar información de depuración), puede usar la opción responseFormatter .
responseFormatter es una función que se pasa al método Fetcher.middleware . Se pasa tres argumentos, el objeto de solicitud, el objeto de respuesta y el objeto de respuesta del servicio (es decir, los datos devueltos de su servicio). La función responseFormatter puede modificar la respuesta del servicio para agregar información adicional.
Echa un vistazo al ejemplo a continuación:
/**
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 ;
} ,
} ) ,
) ; Ahora, cuando se realiza una solicitud, su respuesta contendrá la propiedad debug agregada anteriormente.
Fetchr brinda soporte a CORS al permitirle pasar el host de origen completo a la opción corsPath .
Por ejemplo:
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
corsPath : 'http://www.foo.com' ,
xhrPath : '/fooProxy' ,
} ) ;
fetcher . read ( 'service' ) . params ( { foo : 1 } ) . clientConfig ( { cors : true } ) ; Además, también puede personalizar cómo se construye la URL GET pasando a la propiedad constructGetUri cuando ejecuta su llamada 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 ,
} ) ; Puede proteger sus rutas de middleware Fetchr de los ataques CSRF agregando un middleware frente a ella:
app.use('/myCustomAPIEndpoint', csrf(), Fetcher.middleware());
Puede usar https://github.com/expressjs/csurf para esto como ejemplo.
A continuación, debe asegurarse de que el token CSRF se envíe con nuestras solicitudes para que puedan ser validados. Para hacer esto, pase el token como clave en el objeto options.context Context en el cliente:
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' ,
} ,
} ) ; Este _csrf se enviará en todas las solicitudes del cliente como un parámetro de consulta para que pueda validarse en el servidor.
Al llamar a un servicio de Fetcher, puede pasar un objeto de configuración opcional.
Cuando esta llamada está hecha desde el cliente, el objeto de configuración se usa para establecer algunas opciones de solicitud y se puede usar para anular las opciones predeterminadas:
//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 ) ;Para las solicitudes del servidor, el objeto de configuración simplemente se pasa al servicio que se llama.
Puede configurar FECHRR para volver a intentar automáticamente las solicitudes fallidas especificando una configuración retry en la configuración global o en la configuración de la solicitud:
// Globally
const fetchr = new Fetchr ( {
retry : { maxRetries : 2 } ,
} ) ;
// Per request
fetchr . read ( 'service' ) . clientConfig ( {
retry : { maxRetries : 1 } ,
} ) ; Con la configuración anterior, Fetchr volverá a intentar dos veces todas las solicitudes que fallan, pero solo una vez al llamar read('service') .
Puede personalizar aún más cómo funciona el mecanismo de reintento. Estas son todas las configuraciones y sus valores predeterminados:
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)
}intervalo
El intervalo entre cada solicitud respeta la siguiente fórmula, basada en el retroceso exponencial y la estrategia de fluctuación completa publicada en esta publicación de blog de arquitectura de AWS:
Math . random ( ) * Math . pow ( 2 , attempt ) * interval ; attempt es el número de intento de reintento actual a partir de 0. Por defecto, interval corresponde a 200 ms.
Codos de estado
Por razones históricas, Fetchr solo reemplaza 408 respuestas y no hay respuestas en absoluto (por ejemplo, un error de red, indicado por un código de estado 0). Sin embargo, es posible que también se vuelva útil para volver a intentar otros códigos (502, 503, 504 pueden ser buenos candidatos para reintentos automáticos).
insegurar
Por defecto, Fetchr solo reemplaza las solicitudes read . Esto se hace por razones de seguridad: leer dos veces una entrada de una base de datos no es tan mala como crear una entrada dos veces. Pero si su aplicación o recurso no necesita este tipo de protección, puede permitir reintentos estableciendo unsafeAllowRetry en true y Fetchr volverá a intentar todas las operaciones.
Por defecto, Fetchr agrega todos los valores de contexto a la URL de solicitud como parámetros de consulta. contextPicker le permite tener un mayor control sobre qué variables de contexto se envían como parámetros de consulta dependiendo del método de solicitud ( GET o POST ). Esto es útil cuando desea limitar el número de variables en una URL GET para no almacenar en caché accidentalmente.
contextPicker sigue el mismo formato que el parámetro predicate en lodash/pickBy con dos argumentos: (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
} ,
} ) ; Al llamar a un servicio de Fetcher, puede agregar encabezados de solicitud personalizados.
Una solicitud contiene encabezados personalizados cuando agrega la opción headers a 'ClientConfig'.
const config = {
headers : {
'X-VERSION' : '1.0.0' ,
} ,
} ;
fetcher . read ( 'service' ) . params ( { id : 1 } ) . clientConfig ( config ) ; Todas las solicitudes contienen encabezados personalizados cuando agrega la opción de headers a los argumentos de constructor de 'Fetcher'.
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
headers : {
'X-VERSION' : '1.0.0' ,
} ,
} ) ; Para recopilar las estadísticas de éxito/falla/latencia del servicio de Fetcher, puede configurar statsCollector para Fetchr . La función statsCollector se invocará con un argumento: stats . El objeto stats contendrá los siguientes campos:
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' ,
) ;
} ,
} ) ,
) ; Este software es gratuito bajo el Yahoo! Inc. Licencia BSD. Consulte el archivo de licencia para ver el texto de la licencia y la información de derechos de autor.