Universelle Datenzugriffsschicht für Webanwendungen.
Normalerweise rufen Sie auf dem Server Ihre API oder Datenbank direkt auf, um einige Daten abzurufen. Auf dem Kunden können Sie jedoch nicht immer Ihre Dienste auf die gleiche Weise anrufen (dh die Domänenrichtlinien). Stattdessen müssen XHR/Fetch -Anfragen an den Server gestellt werden, der an Ihren Dienst weitergeleitet wird.
Für beide Umgebungen anders schreiben zu müssen, ist doppelt und fehleranfällig. Fetchr bietet eine Abstraktionsebene über Ihre Datenservice -Aufrufe, damit Sie Daten mit derselben API auf der Server und der Client -Seite abrufen können.
npm install fetchr --save WICHTIG: Wenn Sie auf dem Browser sind, verlässt sich Fetchr vollständig auf Fetch -API. Wenn Sie alte Browser unterstützen müssen, müssen Sie auch eine Polyfill installieren (z. B. https://github.com/github/fetch).
Befolgen Sie die folgenden Schritte, um Fetchr ordnungsgemäß einzurichten. Dies setzt voraus, dass Sie das Express -Framework verwenden.
Fügen Sie auf der Serverseite die Fetchr Middleware in Ihre Express -App an einem benutzerdefinierten API -Endpunkt hinzu.
Fetchr Middleware erwartet, dass Sie die body-parser Middleware (oder eine alternative Middleware, die req.body bevölkert) verwenden, bevor Sie Fetchr Middleware verwenden.
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 ( ) ) ; Auf der Client -Seite ist es erforderlich, dass die xhrPath dem Pfad übereinstimmt, an dem die Middleware im vorherigen Schritt montiert wurde
xhrPath ist eine optionale Konfigurationseigenschaft, mit der Sie den Endpunkt an Ihre Dienste anpassen können, Standardeinstellungen an /api .
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
xhrPath : '/myCustomAPIEndpoint' ,
} ) ; Sie müssen alle Datendienste registrieren, die Sie in Ihrer Anwendung verwenden möchten. Die Schnittstelle für Ihren Dienst ist ein Objekt, das eine resource und mindestens einen CRUD -Betrieb definieren muss. Die resource wird verwendet, wenn Sie einen der CRUD -Operationen anrufen.
// 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 }) {}
} ;Datendienste benötigen möglicherweise Zugriff auf jede einzelne Anfrage, um beispielsweise die aktuelle angemeldete Sitzung des Benutzers zu erhalten. Aus diesem Grund muss Fetcher einmal pro Anfrage instanziiert werden.
Auf der Serverside muss Fetcher in Express Middleware instanziiert werden. Auf dem Clientside muss dies nur auf der Seitenlade auftreten.
// 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
} ) ; Siehe das einfache Beispiel.
Serviceanrufe auf dem Client werden transparent abgerufen. Es ist eine gute Idee, Cache -Header auf allgemeine Fetch -Anrufe festzulegen. Sie können dies tun, indem Sie einen dritten Parameter im Rückruf Ihres Dienstes bereitstellen. Wenn Sie sich ansehen möchten, welche Header durch den gerade angerufenen Dienst festgelegt wurden, inspizieren Sie einfach den dritten Parameter im Rückruf.
HINWEIS: Wenn Sie Versprechen verwenden, ist die Metadaten auf der meta -Eigenschaft des gelösten Werts verfügbar.
// 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
} ) ; Es gibt eine Convenience -Methode namens fetcher.getServiceMeta auf der Fetchr -Instanz. Diese Methode gibt die Metadaten für alle Anrufe zurück, die bisher in einem Array -Format stattgefunden haben. Auf dem Server enthält dies alle Serviceanrufe für die aktuelle Anfrage. Im Kunden enthält dies alle Serviceanrufe für die aktuelle Sitzung.
Normalerweise instanziieren Sie Fetcher mit einigen Standardoptionen für die gesamte Browser -Sitzung. Es kann jedoch Fälle geben, in denen Sie diese Optionen später in derselben Sitzung aktualisieren möchten.
Sie können dies mit der updateOptions -Methode tun:
// Start
const fetcher = new Fetcher ( {
xhrPath : '/myCustomAPIEndpoint' ,
xhrTimeout : 2000 ,
} ) ;
// Later, you may want to update the xhrTimeout
fetcher . updateOptions ( {
xhrTimeout : 4000 ,
} ) ; Wenn in Ihrer Fetchr -Crud -Methode ein Fehler auftritt, sollten Sie ein Fehlerobjekt werfen. Das Fehlerobjekt sollte einen statusCode (Standard 500) und eine output enthalten, die ein serialisierbares JSON -Objekt enthält, das an den Client gesendet wird.
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 ;
} ,
} ;Und in Ihrem Service Anruf:
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'
} ) ; Ein Objekt mit einer abort Methode wird zurückgegeben, wenn Fetchr -Anforderungen auf den Client erstellt werden. Dies ist nützlich, wenn Sie eine Anfrage abbrechen möchten, bevor sie abgeschlossen ist.
const req = fetcher
. read ( 'someData' )
. params ( { id : 42 } )
. catch ( ( err ) => {
// err.reason will be ABORT
} ) ;
req . abort ( ) ; xhrTimeout ist eine optionale Konfigurationseigenschaft, mit der Sie Timeout (in MS) für alle Clientside -Anforderungen festlegen können, die Standardeinstellungen auf 3000 sind. Auf dem Clientside werden Xhrpath und xhrtimeout für alle Anfragen verwendet. Auf der Serverside werden Xhrpath und Xhrtimeout nicht benötigt und ignoriert.
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
xhrPath : '/myCustomAPIEndpoint' ,
xhrTimeout : 4000 ,
} ) ; Wenn Sie eine Zeitüberschreitung pro Anfrage festlegen möchten, können Sie clientConfig mit einer timeout -Immobilie anrufen:
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
} ) ; Für einige Anträge gibt es möglicherweise eine Situation, in der Sie die in der Anfrage übergebenen Serviceparameter verarbeiten müssen, bevor sie an den tatsächlichen Dienst gesendet werden. Normalerweise verarbeiten Sie sie im Dienst selbst. Wenn Sie jedoch die Verarbeitung in vielen Diensten durchführen müssen (dh zur Sicherheit von Sicherheit), können Sie die Option paramsProcessor verwenden.
paramsProcessor ist eine Funktion, die in die Methode Fetcher.middleware übergeben wird. Es wird drei Argumente übergeben, das Anforderungsobjekt, das ServiceInfo -Objekt und das Service -Params -Objekt. Die paramsProcessor -Funktion kann dann die Dienstparameter bei Bedarf ändern.
Hier ist ein Beispiel:
/**
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 ) ;
} ,
} ) ,
) ; Bei einigen Anwendungen kann es eine Situation geben, in der Sie die Antwort ändern müssen, bevor sie an den Client weitergegeben wird. Normalerweise werden Sie Ihre Änderungen im Dienst selbst anwenden. Wenn Sie jedoch die Antworten in vielen Diensten ändern müssen (dh Debug -Informationen hinzuzufügen), können Sie die Option responseFormatter verwenden.
responseFormatter ist eine Funktion, die in die Methode Fetcher.middleware übergeben wird. Es wird drei Argumente übergeben, das Anforderungsobjekt, das Antwortobjekt und das Service -Antwortobjekt (dh die von Ihrem Dienst zurückgegebenen Daten). Die responseFormatter -Funktion kann dann die Serviceantwort ändern, um zusätzliche Informationen hinzuzufügen.
Schauen Sie sich das Beispiel unten an:
/**
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 ;
} ,
} ) ,
) ; Wenn nun eine Anfrage durchgeführt wird, enthält Ihre Antwort die oben hinzugefügte debug -Eigenschaft.
Fetchr bietet CORS -Unterstützung, indem Sie den vollständigen Ursprungs -Host an corsPath -Option übergeben können.
Zum Beispiel:
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
corsPath : 'http://www.foo.com' ,
xhrPath : '/fooProxy' ,
} ) ;
fetcher . read ( 'service' ) . params ( { foo : 1 } ) . clientConfig ( { cors : true } ) ; Darüber hinaus können Sie auch anpassen, wie die GET -URL erstellt wird, indem Sie die constructGetUri -Eigenschaft übergeben, wenn Sie Ihren read ausführen:
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 ,
} ) ; Sie können Ihre Fetchr -Middleware -Pfade vor CSRF -Angriffen schützen, indem Sie eine Middleware vor dem Hinzufügen vor diesem Hinzufügen hinzufügen:
app.use('/myCustomAPIEndpoint', csrf(), Fetcher.middleware());
Dafür können Sie https://github.com/expressjs/csurf als Beispiel verwenden.
Als nächstes müssen Sie sicherstellen, dass das CSRF -Token mit unseren Anfragen gesendet wird, damit sie validiert werden können. Geben Sie dazu das Token als Schlüssel in den options.context über, um den Client zu erhalten.
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' ,
} ,
} ) ; Dieser _csrf wird in allen Client -Anforderungen als Abfrageparameter gesendet, damit er auf dem Server validiert werden kann.
Beim Aufrufen eines Fether -Dienstes können Sie ein optionales Konfigurationsobjekt übergeben.
Wenn dieser Anruf vom Client getätigt wird, wird das Konfigurationsobjekt verwendet, um einige Anforderungsoptionen festzulegen, und kann verwendet werden, um Standardoptionen zu überschreiben:
//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 ) ;Für Anfragen vom Server wird das Konfigurationsobjekt einfach in den aufgerufenen Dienst übergeben.
Sie können Fetchr so einstellen, dass fehlgeschlagene Anforderungen automatisch wiederholt werden, indem Sie eine retry in der globalen oder in der Anforderungskonfiguration angeben:
// Globally
const fetchr = new Fetchr ( {
retry : { maxRetries : 2 } ,
} ) ;
// Per request
fetchr . read ( 'service' ) . clientConfig ( {
retry : { maxRetries : 1 } ,
} ) ; Mit der obigen Konfiguration wird Fetchr zweimal alle Anfragen wiederholen, die fehlschlagen, aber nur einmal beim Aufrufen von read('service') .
Sie können weiter anpassen, wie der Wiederholungsmechanismus funktioniert. Dies sind alle Einstellungen und ihre Standardwerte:
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)
}Intervall
Das Intervall zwischen jeder Anfrage respektiert die folgende Formel, basierend auf der exponentiellen Backoff und der vollständigen Jitter -Strategie, die in diesem AWS -Architektur -Blog -Beitrag veröffentlicht wurde:
Math . random ( ) * Math . pow ( 2 , attempt ) * interval ; attempt ist die Anzahl des aktuellen Wiederholungsversuchs ab 0. Standardmäßig entspricht interval 200 m.
Statuscodes
Aus historischen Gründen wird Fetchr nur 408 Antworten und überhaupt ohne Antworten (z. Es ist jedoch möglicherweise nützlich, auch andere Codes wiederzuholen (502, 503, 504 können gute Kandidaten für automatische Wiederholungen sein).
Unafallowretry
Standardmäßig werden nur Fetchr -Anfragen read . Dies erfolgt aus Sicherheitsgründen: Das Lesen eines Eintrags aus einer Datenbank ist nicht so schlecht wie zweimal ein Eintrag. Wenn Ihre Anwendung oder Ressource diese Art von Schutz nicht benötigt, können Sie Wiederholungen zulassen, indem Sie unsafeAllowRetry auf true einstellen, und Fetchr wird alle Vorgänge wiederholen.
Standardmäßig wendet Fetchr alle Kontextwerte an die Anforderungs -URL als Abfrageparameter an. contextPicker können Sie eine stärkere Kontrolle darüber haben, welche Kontextvariablen je nach Anforderungsmethode ( GET oder POST ) als Abfrageparamien gesendet werden. Dies ist nützlich, wenn Sie die Anzahl der Variablen in einer GET -URL einschränken möchten, um die Büste nicht versehentlich zu leiten.
contextPicker folgt dem gleichen Format wie der predicate in lodash/pickBy mit zwei Argumenten: (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
} ,
} ) ; Wenn Sie einen Fetcher -Dienst anrufen, können Sie benutzerdefinierte Anfrage -Header hinzufügen.
Eine Anfrage enthält benutzerdefinierte Header, wenn Sie "ClientConfig" headers -Option hinzufügen.
const config = {
headers : {
'X-VERSION' : '1.0.0' ,
} ,
} ;
fetcher . read ( 'service' ) . params ( { id : 1 } ) . clientConfig ( config ) ; Alle Anfragen enthalten benutzerdefinierte Header, wenn Sie Konstruktorargumente von 'Fetcher' headers hinzufügen.
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
headers : {
'X-VERSION' : '1.0.0' ,
} ,
} ) ; Um den Erfolgs-/Ausfall-/Latenz -Statistiken von Fetcher Service zu sammeln, können Sie statsCollector für Fetchr konfigurieren. Die statsCollector -Funktion wird mit einer Argumente aufgerufen: stats . Das stats enthält die folgenden Felder:
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' ,
) ;
} ,
} ) ,
) ; Diese Software kann unter dem Yahoo! Inc. BSD -Lizenz. In der Lizenzdatei für Lizenztext und Copyright -Informationen finden Sie.