Camada universal de acesso a dados para aplicativos da Web.
Normalmente no servidor, você chama sua API ou banco de dados diretamente para buscar alguns dados. No entanto, no cliente, você nem sempre pode ligar para seus serviços da mesma maneira (ou seja, políticas de domínio cruzado). Em vez disso, precisam ser feitas solicitações de XHR/busca ao servidor que avançam para o seu serviço.
Ter que escrever código de maneira diferente para ambos os ambientes é duplicado e propenso a erros. O Fetchr fornece uma camada de abstração sobre as chamadas de serviço de dados para que você possa buscar dados usando a mesma API no lado do servidor e do cliente.
npm install fetchr --save IMPORTANTE: Quando no navegador, Fetchr depende totalmente da API Fetch . Se você precisar suportar navegadores antigos, também precisará instalar um poli -preenchimento (por exemplo, https://github.com/github/fetch).
Siga as etapas abaixo para configurar o Fetchr corretamente. Isso pressupõe que você esteja usando a estrutura expressa.
No lado do servidor, adicione o middleware Fetchr ao seu aplicativo Express em um terminal de API personalizado.
O Middleware da Fetchr espera que você esteja usando o middleware body-parser (ou um middleware alternativo que preenche req.body ) antes de usar o 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 ( ) ) ; No lado do cliente, é necessário que a opção xhrPath corresponda ao caminho onde o middleware foi montado na etapa anterior
xhrPath é uma propriedade de configuração opcional que permite personalizar o terminal de seus serviços, padrão para /api .
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
xhrPath : '/myCustomAPIEndpoint' ,
} ) ; Você precisará registrar quaisquer serviços de dados que desejar usar em seu aplicativo. A interface do seu serviço será um objeto que deve definir uma propriedade resource e pelo menos uma operação CRUD. A propriedade resource será usada quando você ligar para uma das operações da 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 }) {}
} ;Os serviços de dados podem precisar de acesso a cada solicitação individual, por exemplo, para obter a sessão atual do usuário. Por esse motivo, o Fetcher deverá ser instanciado uma vez por solicitação.
No servidor, isso exige que o Fetcher seja instanciado por solicitação, no Middleware Express. No lado do cliente, isso só precisa acontecer na carga da 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
} ) ; Veja o exemplo simples.
As chamadas de serviço ao cliente se tornam transparentemente solicitações de busca. É uma boa ideia definir cabeçalhos de cache em chamadas de busca comum. Você pode fazer isso fornecendo um terceiro parâmetro no retorno de chamada do seu serviço. Se você quiser olhar para quais cabeçalhos foram definidos pelo serviço que você acabou de ligar, basta inspecionar o terceiro parâmetro no retorno de chamada.
NOTA: Se você estiver usando promessas, os metadados estarão disponíveis na propriedade meta do valor resolvido.
// 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
} ) ; Existe um método de conveniência chamado fetcher.getServiceMeta na instância do Fetchr. Este método retornará os metadados para todas as chamadas que aconteceram até agora em um formato de matriz. No servidor, isso incluirá todas as chamadas de serviço para a solicitação atual. No cliente, isso incluirá todas as chamadas de serviço para a sessão atual.
Normalmente, você instancia o Fetcher com algumas opções padrão para toda a sessão do navegador, mas pode haver casos em que você deseja atualizar essas opções mais adiante na mesma sessão.
Você pode fazer isso com o método updateOptions :
// Start
const fetcher = new Fetcher ( {
xhrPath : '/myCustomAPIEndpoint' ,
xhrTimeout : 2000 ,
} ) ;
// Later, you may want to update the xhrTimeout
fetcher . updateOptions ( {
xhrTimeout : 4000 ,
} ) ; Quando ocorre um erro no seu método Fetchr Crud, você deve lançar um objeto de erro. O objeto de erro deve conter um statusCode (padrão 500) e a propriedade output que contém um objeto serializável JSON que será enviado ao 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 ;
} ,
} ;E em sua chamada de serviço:
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'
} ) ; Um objeto com um método abort é retornado ao criar solicitações de busca no cliente. Isso é útil se você deseja abortar uma solicitação antes de ser concluída.
const req = fetcher
. read ( 'someData' )
. params ( { id : 42 } )
. catch ( ( err ) => {
// err.reason will be ABORT
} ) ;
req . abort ( ) ; xhrTimeout é uma propriedade de configuração opcional que permite definir o tempo limite (no MS) para todas as solicitações do cliente, padrão para 3000 . No cliente, xhrpath e xhrtimeout serão usados para todas as solicitações. No servidor, xhrpath e xhrtimeout não são necessários e são ignorados.
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
xhrPath : '/myCustomAPIEndpoint' ,
xhrTimeout : 4000 ,
} ) ; Se você deseja definir um tempo limite por solicitação, ligue para clientConfig com uma propriedade 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 alguns aplicativos, pode haver uma situação em que você precisa processar os parâmetros de serviço passados na solicitação antes de serem enviados ao serviço real. Normalmente, você os processaria no próprio serviço. No entanto, se você precisar executar o processamento em muitos serviços (por exemplo, haixa por segurança), poderá usar a opção paramsProcessor .
paramsProcessor é uma função que é passada para o método Fetcher.middleware . Passou três argumentos, o objeto de solicitação, o objeto ServiceInfo e o objeto Params de serviço. A função paramsProcessor pode modificar os parâmetros de serviço, se necessário.
Aqui está um exemplo:
/**
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 alguns aplicativos, pode haver uma situação em que você precisa modificar a resposta antes de ser passada para o cliente. Normalmente, você aplica suas modificações no próprio serviço. No entanto, se você precisar modificar as respostas em muitos serviços (ou seja, adicione informações de depuração), poderá usar a opção responseFormatter .
responseFormatter é uma função que é passada para o método Fetcher.middleware . Passou três argumentos, o objeto de solicitação, o objeto de resposta e o objeto de resposta do serviço (ou seja, os dados retornados do seu serviço). A função responseFormatter pode modificar a resposta do serviço para adicionar informações adicionais.
Dê uma olhada no exemplo abaixo:
/**
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 ;
} ,
} ) ,
) ; Agora, quando uma solicitação for executada, sua resposta conterá a propriedade debug adicionada acima.
O Fetchr fornece suporte ao CORS, permitindo que você passe o host de origem completo na opção corsPath .
Por exemplo:
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
corsPath : 'http://www.foo.com' ,
xhrPath : '/fooProxy' ,
} ) ;
fetcher . read ( 'service' ) . params ( { foo : 1 } ) . clientConfig ( { cors : true } ) ; Além disso, você também pode personalizar como o URL GET é construído passando na propriedade constructGetUri ao executar sua chamada 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 ,
} ) ; Você pode proteger seus caminhos de middleware do Fetchr dos ataques de CSRF adicionando um middleware na frente dele:
app.use('/myCustomAPIEndpoint', csrf(), Fetcher.middleware());
Você pode usar https://github.com/expressjs/csurf para isso como exemplo.
Em seguida, você precisa garantir que o token do CSRF esteja sendo enviado com nossas solicitações para que elas possam ser validadas. Para fazer isso, passe o token como uma chave no objeto options.context no 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 será enviado em todas as solicitações do cliente como um parâmetro de consulta para que possa ser validado no servidor.
Ao chamar um serviço de busca, você pode passar um objeto de configuração opcional.
Quando esta chamada é feita do cliente, o objeto Config é usado para definir algumas opções de solicitação e pode ser usado para substituir as opções padrão:
//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 solicitações do servidor, o objeto de configuração é simplesmente transmitido para o serviço que está sendo chamado.
Você pode definir o FETCHR para repetir automaticamente solicitações com falha, especificando uma configuração retry na configuração global ou na solicitação:
// Globally
const fetchr = new Fetchr ( {
retry : { maxRetries : 2 } ,
} ) ;
// Per request
fetchr . read ( 'service' ) . clientConfig ( {
retry : { maxRetries : 1 } ,
} ) ; Com a configuração acima, o FETCHR novamente novamente, todas as solicitações que falham, mas apenas uma vez ao ligar read('service') .
Você pode personalizar ainda como o mecanismo de tentativa funciona. Estas são todas as configurações e seus valores padrão:
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
O intervalo entre cada solicitação respeita a seguinte fórmula, com base no backoff exponencial e na estratégia completa da jitter publicada nesta postagem do blog de arquitetura da AWS:
Math . random ( ) * Math . pow ( 2 , attempt ) * interval ; attempt é o número da tentativa atual de tentativa, a partir de 0. Por interval padrão, corresponde a 200ms.
StatusCodes
Por razões históricas, o FETCHR apenas experimenta 408 respostas e nenhuma resposta (por exemplo, um erro de rede, indicado por um código de status 0). No entanto, você pode achar útil também tentar novamente em outros códigos (502, 503, 504 pode ser um bom candidato para tentativas automáticas).
UNSAFEALLOWRETRY
Por padrão, o Fetchr apenas experimenta solicitações read . Isso é feito por razões de segurança: ler duas vezes uma entrada de um banco de dados não é tão ruim quanto criar uma entrada duas vezes. Mas se o seu aplicativo ou recurso não precisar desse tipo de proteção, você poderá permitir tentativas definindo unsafeAllowRetry como true e o FETCHR fará novamente todas as operações.
Por padrão, o Fetchr anexa todos os valores de contexto ao URL da solicitação como parâmetros de consulta. contextPicker permite que você tenha um controle maior sobre quais variáveis de contexto são enviadas como parâmetros de consulta, dependendo do método de solicitação ( GET ou POST ). Isso é útil quando você deseja limitar o número de variáveis em um URL GET para não ser um busto de cache acidentalmente.
contextPicker segue o mesmo formato que o parâmetro predicate em lodash/pickBy com dois 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
} ,
} ) ; Ao ligar para um serviço Fetcher, você pode adicionar cabeçalhos de solicitação personalizados.
Uma solicitação contém cabeçalhos personalizados quando você adiciona a opção headers ao 'ClientConfig'.
const config = {
headers : {
'X-VERSION' : '1.0.0' ,
} ,
} ;
fetcher . read ( 'service' ) . params ( { id : 1 } ) . clientConfig ( config ) ; Todas as solicitações contêm cabeçalhos personalizados quando você adiciona a opção headers para argumentos construtores de 'Fetcher'.
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
headers : {
'X-VERSION' : '1.0.0' ,
} ,
} ) ; Para coletar estatísticas de sucesso/falha/latência do Fetcher Service, você pode configurar statsCollector para Fetchr . A função statsCollector será invocada com um argumento: stats . O objeto stats conterá os seguintes 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 é gratuito para usar no Yahoo! Inc. Licença BSD. Consulte o arquivo de licença para obter informações sobre texto e direitos autorais.