Couche universelle d'accès aux données pour les applications Web.
En règle générale, sur le serveur, vous appelez votre API ou votre base de données directement pour récupérer certaines données. Cependant, sur le client, vous ne pouvez pas toujours appeler vos services de la même manière (c'est-à-dire des politiques de domaine croisé). Au lieu de cela, les demandes XHR / Fetch doivent être faites au serveur qui sont transmis à votre service.
Devoir écrire du code différemment pour les deux environnements est le duplication et les erreurs sujettes. Fetchr fournit une couche d'abstraction sur vos appels de service de données afin que vous puissiez récupérer les données en utilisant la même API du côté serveur et client.
npm install fetchr --save IMPORTANT: En cas de navigateur, Fetchr s'appuie pleinement sur l'API Fetch . Si vous devez prendre en charge les anciens navigateurs, vous devrez également installer un polyfill (par exemple, https://github.com/github/fetch).
Suivez les étapes ci-dessous pour configurer correctement Fetchr. Cela suppose que vous utilisez le cadre express.
Côté serveur, ajoutez le middleware Fetchr dans votre application Express à un point de terminaison API personnalisé.
Fetchr Middleware s'attend à ce que vous utilisez le middleware body-parser (ou un middleware alternatif qui remplit req.body ) avant d'utiliser Fetchr Middleware.
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 ( ) ) ; Côté client, il est nécessaire que l'option xhrPath corresponde au chemin où le middleware a été monté à l'étape précédente
xhrPath est une propriété de configuration facultative qui vous permet de personnaliser le point de terminaison à vos services, par défaut API /api .
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
xhrPath : '/myCustomAPIEndpoint' ,
} ) ; Vous devrez enregistrer tous les services de données que vous souhaitez utiliser dans votre application. L'interface de votre service sera un objet qui doit définir une propriété resource et au moins une opération CRUD. La propriété resource sera utilisée lorsque vous appellerez l'une des opérations 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 }) {}
} ;Les services de données peuvent avoir besoin d'accéder à chaque demande individuelle, par exemple, pour obtenir la session de l'utilisateur connecté actuel. Pour cette raison, Fetcher devra être instancié une fois par demande.
Sur le bord de serveurs, cela nécessite que Fetcheur soit instancié par demande, en middleware express. Sur le côté des clients, cela ne doit se produire qu'à la charge de la page.
// 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
} ) ; Voir l'exemple simple.
Les appels de service sur le client deviennent transparents de manière transparente. C'est une bonne idée de définir des en-têtes de cache sur les appels de récupération communs. Vous pouvez le faire en fournissant un troisième paramètre dans le rappel de votre service. Si vous souhaitez regarder les en-têtes définis par le service que vous venez d'appeler, inspectez simplement le troisième paramètre du rappel.
Remarque: Si vous utilisez des promesses, les métadonnées seront disponibles sur la meta propriété de la valeur résolue.
// 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
} ) ; Il existe une méthode de commodité appelée fetcher.getServiceMeta sur l'instance fetchr. Cette méthode renvoie les métadonnées pour tous les appels qui se sont produits jusqu'à présent dans un format de tableau. Dans le serveur, cela inclura tous les appels de service pour la demande actuelle. Dans le client, cela inclura tous les appels de service pour la session en cours.
Habituellement, vous instanciez Fetcher avec certaines options par défaut pour toute la session du navigateur, mais il peut y avoir des cas où vous souhaitez mettre à jour ces options plus tard dans la même session.
Vous pouvez le faire avec la méthode updateOptions :
// Start
const fetcher = new Fetcher ( {
xhrPath : '/myCustomAPIEndpoint' ,
xhrTimeout : 2000 ,
} ) ;
// Later, you may want to update the xhrTimeout
fetcher . updateOptions ( {
xhrTimeout : 4000 ,
} ) ; Lorsqu'une erreur se produit dans votre méthode Fetchr CUD, vous devez lancer un objet d'erreur. L'objet d'erreur doit contenir une propriété statusCode (par défaut 500) et output qui contient un objet SERIALISABLE JSON qui sera envoyé au client.
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 ;
} ,
} ;Et dans votre service: Call:
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'
} ) ; Un objet avec une méthode abort est renvoyé lors de la création de demandes de fetchr sur le client. Ceci est utile si vous souhaitez interrompre une demande avant sa fin.
const req = fetcher
. read ( 'someData' )
. params ( { id : 42 } )
. catch ( ( err ) => {
// err.reason will be ABORT
} ) ;
req . abort ( ) ; xhrTimeout est une propriété de configuration facultative qui vous permet de définir le délai d'attente (en ms) pour toutes les demandes de clients, par défaut, 3000 . Sur ClientSide, XHRPATH et XHRTimeout seront utilisés pour toutes les demandes. Sur le bord de serveurs, Xhrpath et Xhrtimeout ne sont pas nécessaires et sont ignorés.
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
xhrPath : '/myCustomAPIEndpoint' ,
xhrTimeout : 4000 ,
} ) ; Si vous souhaitez définir un délai d'attente par demande, vous pouvez appeler clientConfig avec une propriété 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
} ) ; Pour certaines applications, il peut y avoir une situation où vous devez traiter les paramètres de service adoptés dans la demande avant d'être envoyés au service réel. En règle générale, vous les traiteriez dans le service lui-même. Cependant, si vous avez besoin d'effectuer un traitement sur de nombreux services (c.-à-d. Assaisonnement pour la sécurité), vous pouvez utiliser l'option paramsProcessor .
paramsProcessor est une fonction qui est transmise dans la méthode Fetcher.middleware . Il a transmis trois arguments, l'objet de demande, l'objet ServiceInfo et l'objet de paramètres du service. La fonction paramsProcessor peut ensuite modifier les paramètres de service si nécessaire.
Voici un exemple:
/**
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 ) ;
} ,
} ) ,
) ; Pour certaines applications, il peut y avoir une situation où vous devez modifier la réponse avant qu'elle ne soit transmise au client. En règle générale, vous appliqueriez vos modifications dans le service lui-même. Cependant, si vous devez modifier les réponses sur de nombreux services (c.-à-d. Ajouter des informations de débogage), vous pouvez utiliser l'option responseFormatter .
responseFormatter est une fonction qui est transmise dans la méthode Fetcher.middleware . Il a adopté trois arguments, l'objet de demande, l'objet de réponse et l'objet de réponse de service (c'est-à-dire les données renvoyées de votre service). La fonction responseFormatter peut ensuite modifier la réponse du service pour ajouter des informations supplémentaires.
Jetez un œil à l'exemple ci-dessous:
/**
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 ;
} ,
} ) ,
) ; Maintenant, lorsqu'une demande est effectuée, votre réponse contiendra la propriété debug ajoutée ci-dessus.
Fetchr fournit le support CORS en vous permettant de passer l'hôte pleine d'origine dans l'option corsPath .
Par exemple:
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
corsPath : 'http://www.foo.com' ,
xhrPath : '/fooProxy' ,
} ) ;
fetcher . read ( 'service' ) . params ( { foo : 1 } ) . clientConfig ( { cors : true } ) ; De plus, vous pouvez également personnaliser la façon dont l'URL GET est construite en transmettant la propriété constructGetUri lorsque vous exécutez votre appel 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 ,
} ) ; Vous pouvez protéger vos chemins de middleware Fetchr contre les attaques CSRF en ajoutant un middleware devant lui:
app.use('/myCustomAPIEndpoint', csrf(), Fetcher.middleware());
Vous pouvez utiliser https://github.com/expressjs/csurf pour cela comme exemple.
Ensuite, vous devez vous assurer que le jeton CSRF est envoyé avec nos demandes afin qu'ils puissent être validés. Pour ce faire, passer le jeton comme une clé dans l'objet options.context sur le client:
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' ,
} ,
} ) ; Ce _csrf sera envoyé dans toutes les demandes du client en tant que paramètre de requête afin qu'il puisse être validé sur le serveur.
Lorsque vous appelez un service de récupérateur, vous pouvez passer un objet de configuration facultatif.
Lorsque cet appel est passé à partir du client, l'objet config est utilisé pour définir certaines options de demande et peut être utilisé pour remplacer les options par défaut:
//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 ) ;Pour les demandes du serveur, l'objet config est simplement transmis dans le service appelé.
Vous pouvez définir Fetchr pour réessayer automatiquement les demandes d'échec en spécifiant une configuration retry dans la configuration globale ou dans la demande:
// Globally
const fetchr = new Fetchr ( {
retry : { maxRetries : 2 } ,
} ) ;
// Per request
fetchr . read ( 'service' ) . clientConfig ( {
retry : { maxRetries : 1 } ,
} ) ; Avec la configuration ci-dessus, Fetchr réessayera deux fois toutes les demandes qui échouent mais une seule fois lors de l'appel read('service') .
Vous pouvez en outre personnaliser le fonctionnement du mécanisme de réessayer. Ce sont tous des paramètres et leurs valeurs par défaut:
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)
}intervalle
L'intervalle entre chaque demande respecte la formule suivante, basée sur le backoff exponentiel et la stratégie complète de la gigue publiée dans cet article de blog sur l'architecture AWS:
Math . random ( ) * Math . pow ( 2 , attempt ) * interval ; attempt est le nombre de la tentative de réessayer en cours à partir de 0. Par défaut, interval correspond à 200 ms.
codes de statut
Pour des raisons historiques, Fetchr récupère uniquement les réponses 408 et aucune réponse (par exemple, une erreur de réseau, indiquée par un code d'état 0). Cependant, vous pouvez également trouver utile de réessayer sur d'autres codes (502, 503, 504 peuvent être de bons candidats pour une tentative automatique).
sans valeur
Par défaut, Fetchr récupère uniquement les demandes read . Cela est fait pour des raisons de sécurité: la lecture de deux fois une entrée d'une base de données n'est pas aussi mauvaise que de créer une entrée deux fois. Mais si votre application ou votre ressource n'a pas besoin de ce type de protection, vous pouvez autoriser les tentatives en définissant unsafeAllowRetry sur true et Fetchr réessayera toutes les opérations.
Par défaut, Fetchr ajoute toutes les valeurs de contexte à l'URL de demande en tant que paramètres de requête. contextPicker vous permet d'avoir un plus grand contrôle sur les variables de contexte envoyées sous forme de paramètres de requête en fonction de la méthode de demande ( GET ou POST ). Ceci est utile lorsque vous souhaitez limiter le nombre de variables dans une URL GET afin de ne pas mettre en cache accidentellement le buste.
contextPicker suit le même format que le paramètre predicate dans lodash/pickBy avec deux arguments: (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
} ,
} ) ; Lorsque vous appelez un service de récupérateur, vous pouvez ajouter des en-têtes de demande personnalisés.
Une demande contient des en-têtes personnalisés lorsque vous ajoutez une option headers à «ClientConfig».
const config = {
headers : {
'X-VERSION' : '1.0.0' ,
} ,
} ;
fetcher . read ( 'service' ) . params ( { id : 1 } ) . clientConfig ( config ) ; Toutes les demandes contiennent des en-têtes personnalisés lorsque vous ajoutez une option headers aux arguments du constructeur de «Fetchher».
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
headers : {
'X-VERSION' : '1.0.0' ,
} ,
} ) ; Pour collecter les statistiques Succès / échec / la latence du service Fettcher, vous pouvez configurer statsCollector pour Fetchr . La fonction statsCollector sera invoquée avec un argument: stats . L'objet stats contiendra les champs suivants:
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' ,
) ;
} ,
} ) ,
) ; Ce logiciel est gratuit sous le Yahoo! Inc. Licence BSD. Voir le fichier de licence pour le texte de licence et les informations sur le droit d'auteur.