UMI request network request path
background
In the process of developing middle office business applications, we found the following problems on the request link:
- Request libraries are diverse and not unified. Each new application needs to repeatedly implement a set of request layer logic. When switching applications, you need to re learn the request library API.
- The design of each application interface is inconsistent and chaotic. The front and rear end students need to redesign the interface format every time. The front-end students need to understand the interface format again before they can do business development when switching applications.
- Interface document maintenance. Some are on YuQue (cloud knowledge base), some are on RAP (open source interface management tool), and some can only know by reading the source code. It is a waste of manpower in terms of maintenance, mock data and communication.
To solve the above problems, we propose request layer governance, hoping to improve and standardize the first, middle and last three stages of the request link through the three steps of unified request library, standardized request interface design specification and unified interface document, So as to reduce the communication and labor costs spent by developers on interface design, document maintenance and request layer logic development. Among them, as the bottom technical support, the unified request library needs to lay a good base in advance to provide stable and perfect functional support for the upper layer. Based on this, UMI request came into being.
umi-request
umi-request It is an open source http request Library Based on fetch encapsulation. It aims to provide developers with a unified API call mode, simplify the use mode, and provide common functions of the request layer:
- Automatic serialization of URL parameters
- Simplified POST data submission
- Response return processing simplified
- Request timeout processing
- Request cache support
- GBK encoding processing
- Unified error handling
- Request to cancel support
- Node environment http request
- Interceptor mechanism
- Onion middleware mechanism
What are the similarities and differences with fetch and axios?
The bottom layer of UMI request discards the XMLHttpRequest with rough design and non separation of concerns, and chooses the fetch (more details) which is more semantic and based on the standard Promise implementation See details ); At the same time, isomorphism is more convenient and easy to use isomorphic-fetch (currently built in); Based on each business application scenario, common request capabilities are extracted and fast configuration is supported, such as post simplification, pre suffix, error checking, etc.
Easy to use
install
npm install --save umi-request
Execute GET request
import request from "umi-request"; request .get("/api/v1/xxx?id=1") .then(function(response) { console.log(response); }) .catch(function(error) { console.log(error); }); // You can also put the URL parameters in options.params request .get("/api/v1/xxx", { params: { id: 1 } }) .then(function(response) { console.log(response); }) .catch(function(error) { console.log(error); });
implement POST request
import request from "umi-request"; request .post("/api/v1/user", { data: { name: "Mike" } }) .then(function(response) { console.log(response); }) .catch(function(error) { console.log(error); });
Instantiate common configuration
Requests generally have some general configurations. We don't want to add them one by one in each request, such as general prefix, suffix, header information, exception handling, etc extend To create a new instance of UMI request to reduce the amount of duplicate code:
import { extend } from "umi-request"; const request = extend({ prefix: "/api/v1", suffix: ".json", timeout: 1000, headers: { "Content-Type": "multipart/form-data" }, params: { token: "xxx" // All requests take the token parameter by default }, errorHandler: function(error) { /* exception handling */ } }); request .get("/user") .then(function(response) { console.log(response); }) .catch(function(error) { console.log(error); });
Built in common request capability
fetch itself does not provide such capabilities as request timeout, caching and cancellation, but is often required in business development. Therefore, UMI request encapsulates and builds common request capabilities to reduce repeated development:
{ // 'params' is the URL parameter to be sent together with the request. The parameter will be automatically encode d and added to the URL // Type must be Object object or URLSearchParams Object params: { id: 1 }, // 'paramsSerializer' developers can serialize params through this function (Note: at this time, the passed in params is an Object that combines the params parameters in extensions. If the passed in URLSearchParams Object will be transformed into an Object object Object paramsSerializer: function (params) { return Qs.stringify(params, { arrayFormat: 'brackets' }) }, // 'data 'is the data sent as the request body // Applicable to these request methods' put ',' post ', and' PATCH ' // Must be one of the following types: // - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams // -Browser specific: FormData, File, Blob // -Node exclusive: Stream data: { name: 'Mike' }, // 'headers' request header headers: { 'Content-Type': 'multipart/form-data' }, // 'timeout 'specifies the number of milliseconds the request timed out (0 means no timeout) // If the request exceeds the 'timeout' time, the request will be interrupted and a request exception will be thrown timeout: 1000, // 'prefix' prefix, which uniformly sets the url prefix // ( e.g. request('/user/save', { prefix: '/api/v1' }) => request('/api/v1/user/save') ) prefix: '', // 'suffix ', uniformly set url suffix // ( e.g. request('/api/v1/user/save', { suffix: '.json'}) => request('/api/v1/user/save.json') ) suffix: '', // 'credentials' send request with credentials // In order for the browser to send requests containing credentials (even cross domain sources), you need to set credentials: 'include' // If you only want to send credentials when the request URL is at the same origin as the calling script, add credentials: 'same origin' // To ensure that the browser does not include credentials in the request instead, use credentials: 'omit' credentials: 'same-origin', // default // Whether 'useCache' uses cache. When the value is true, the GET request will be cached within ttl milliseconds. The only key of the cache policy is the combination of url + params useCache: false, // default // 'ttl 'cache duration (MS), 0 is not expired ttl: 60000, // 'maxCache 'maximum number of caches, 0 is unlimited maxCache: 0, // 'charset' this parameter can be used when the data code type returned by the server is gbk. UMI request will be parsed according to gbk code to avoid random code. The default is utf8 // This parameter is invalid when the parseResponse value is false charset: 'gbk', // 'responseType ': how to parse the returned data. This parameter is invalid when the parseResponse value is false // The default is' json ', and the returned result is parsed by response. Text(). Then (d = > json. Parse (d)) // For others (text, blob, arrayBuffer, formData), perform Response[responseType] () parsing responseType: 'json', // default // 'errorHandler' provides unified exception handling for developers to handle exceptions in requests. Please refer to the error handling document below for details errorHandler: function(error) { /* exception handling */ }, }
Middleware mechanism is easy to expand
Complex scenario applications have customized processing requirements before and after requests. In addition to providing basic built-in capabilities, the request library also needs to improve its expansibility. Based on this UMI request, the middleware mechanism is introduced and the onion ring model similar to KOA is selected:
(Fig. 1)
As can be seen from the above figure, each layer of onion ring is a middleware, and the request will be executed twice after passing through a middleware. Developers can easily implement enhanced processing before and after the request according to business requirements:
import request from "umi-request"; request.use(function(ctx, next) { console.log("a1"); return next().then(function() { console.log("a2"); }); }); request.use(function(ctx, next) { console.log("b1"); return next().then(function() { console.log("b2"); }); }); // The execution sequence is as follows: // a1 -> b1 -> b2 -> a2 // Using async/await can make the structure and sequence clearer: request.use(async (ctx, next) => { console.log("a1"); await next(); console.log("a2"); }); request.use(async (ctx, next) => { console.log("b1"); await next(); console.log("b2"); }); const data = await request("/api/v1/a"); // The execution sequence is as follows: // a1 -> b1 -> b2 -> a2
Implementation principle
How to implement the middleware mechanism of onion ring? It is mainly composed of middleware array and middleware combination. The former is responsible for storing the mounted middleware, and the latter is responsible for combining the middleware according to the structure of onion and returning the real executable function:
Storage Middleware
class Onion { constructor() { this.middlewares = []; } // Storage Middleware use(newMiddleware) { this.middlewares.push(newMiddleware); } // Execution Middleware execute(params = null) { const fn = compose(this.middlewares); return fn(params); } }
Composite Middleware
In the above code compose That is, the function implementation of composite middleware. The simplified logic is as follows( See details):
export default function compose(middlewares) { return function wrapMiddlewares(params) { let index = -1; function dispatch(i) { index = i; const fn = middlewares[i]; if (!fn) return Promise.resolve(); return Promise.resolve(fn(params, () => dispatch(i + 1))); } return dispatch(0); }; }
The compose function passes dispatch(0) The first middleware is executed first fn(params, () => dispatch(i +1)) , And provide () => dispatch(i +1) As an input, each middleware can come back after the execution of the next middleware and continue to process its own transactions until all middleware are completed Promise.resolve() , Form onion ring middleware mechanism.
Rich request capability
Facing multi terminal and multi device applications, UMI request not only supports browser http requests, but also meets node environment, custom kernel requests and other capabilities.
Support node environment to send http requests
Request support for node environment based on isomorphic fetch:
const umi = require("umi-request"); const extendRequest = umi.extend({ timeout: 10000 }); extendRequest("/api/user") .then(res => { console.log(res); }) .catch(err => { console.log(err); });
Support custom kernel request capability
Mobile applications generally have their own request protocols, such as RPC requests. The front end will call the client request API through the SDK. UMI request supports developers to encapsulate their request capabilities. Examples:
// service/some.js import request from "umi-request"; // Custom request kernel Middleware function SDKRequest(ctx, next) { const { req } = ctx; const { url, options } = req; const { __umiRequestCoreType__ = "normal" } = options; if (__umiRequestCoreType__.toLowerCase() !== "SDKRequest") { return next(); } return Promise.resolve() .then(() => { return SDK.request(url, options); // It is assumed that the SDK has been introduced and the corresponding request can be initiated through the SDK }) .then(result => { ctx.res = result; // Inject the results into the res of ctx return next(); }); } request.use(SDKRequest, { core: true }); // Introducing kernel Middleware export async function queryUser() { return request("/api/sdk/request", { __umiRequestCoreType__: "SDKRequest", // Declare that SDKRequest is used to initiate the request data: [] }); }
summary
With the improvement of UMI request capability, it has been able to support the requests of various scenarios and end applications. The front-end development only needs to master a set of API calls to realize multi-end development. It no longer needs to pay attention to the implementation of the underlying protocol and focus more on the front-end development. Based on this, UMI request can do more things at the bottom, such as mock data, automatic identification of request type, interface exception monitoring and reporting, interface specification verification, etc., and finally achieve the goal of request governance. There are many capabilities of UMI request that are not mentioned in the article. If you are interested, please check it out Detailed documentation.