Web应用程序的通用数据访问层。
通常,在服务器上,您可以直接调用API或数据库以获取某些数据。但是,在客户端,您不能总是以相同的方式调用您的服务(即跨域政策)。相反,需要向已转发到您的服务的服务器提出XHR/Fetch请求。
在两个环境中必须以不同的方式编写代码是重复的,并且容易出错。 Fetchr在数据服务调用上提供了一个抽象层,因此您可以使用服务器和客户端上的相同API获取数据。
npm install fetchr --save重要的是:在浏览器上, Fetchr完全依赖Fetch API。如果您需要支持旧浏览器,则还需要安装多填充(例如https://github.com/github/fetch)。
请按照以下步骤正确设置fetchr。这假设您正在使用Express框架。
在服务器端,将Fetchr中间件添加到自定义API端点的Express应用中。
Fetchr中间件期望您在使用Fetchr中间件之前使用body-parser中间件(或填充req.body的替代中间件)。
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操作的对象。当您调用CRUD操作之一时,将使用resource属性。
// 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进行实例化。
在Serveride上,这需要在Express Mifdreware中根据请求实例化Fetcher。在客户端,这只需要在页面加载上发生。
// 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'
} ) ; 在客户端创建fetchr请求时,返回具有abort方法的对象。如果您想在请求完成之前中止该请求,这将很有用。
const req = fetcher
. read ( 'someData' )
. params ( { id : 42 } )
. catch ( ( err ) => {
// err.reason will be ABORT
} ) ;
req . abort ( ) ; xhrTimeout是一个可选的配置属性,允许您为所有客户端请求设置超时(在MS中),默认为3000 。在客户端,XHRPATH和XHRTIMEOUT将用于所有请求。在Serveride上,不需要XHRPATH和XHRTIMEOUT,并且被忽略。
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
xhrPath : '/myCustomAPIEndpoint' ,
xhrTimeout : 4000 ,
} ) ;如果要设置每个请求的超时,则可以使用timeout属性调用clientConfig :
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对象和服务参数对象。然后, 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通过允许您将完整的Origin主机传递到corsPath选项中来提供CORS支持。
例如:
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
corsPath : 'http://www.foo.com' ,
xhrPath : '/fooProxy' ,
} ) ;
fetcher . read ( 'service' ) . params ( { foo : 1 } ) . clientConfig ( { cors : true } ) ;此外,您还可以自定义如何通过执行read呼叫时通过在constructGetUri属性中构建GET URL:
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服务时,您可以传递可选的配置对象。
从客户端进行此调用时,配置对象用于设置一些请求选项,可用于覆盖默认选项:
//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 ) ;对于服务器的请求,简单地将配置对象传递到正在调用的服务中。
您可以通过在全局或请求配置中指定retry配置来将fetchr设置为自动重试失败的请求:
// 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为200ms。
状态代码
由于历史原因,Fetchr仅重新检索408个响应,根本没有响应(例如,由状态代码0表示的网络错误)。但是,您可能还会发现对其他代码也重试(502、503、504可能是自动恢复的良好候选者)。
Undafeallowretry
默认情况下,fetchr仅重新检验read请求。这是出于安全原因而进行的:从数据库中阅读两次条目并不像创建两次条目那样糟糕。但是,如果您的应用程序或资源不需要这种保护,则可以通过将unsafeAllowRetry设置为true允许重试,Fetchr将重试所有操作。
默认情况下,Fetchr将所有上下文值附加到请求URL作为查询参数。 contextPicker允许您对哪些上下文变量作为查询参数(根据请求方法( GET或POST )的不同)提供更大的控制。当您想限制GET URL中的变量数量以便不要意外缓存胸围时,这很有用。
contextPicker遵循与lodash/pickBy中的predicate参数相同的格式,并带有两个参数: (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的成功/失败/延迟统计信息,您可以为Fetchr配置statsCollector 。 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许可证。有关许可文本和版权信息,请参见许可证文件。