To highlight

What you can learn from this article:

  • The HTTP status code
  • 200 and 304 caches
  • Memory cache and Dist cache
  • Axios processing of response
  • Koajs simulates practice Http status codes
  • Postman Mock Server

The cause of

Some time ago, the back end and test suggested that our interface error message was always “network error, please try again later”, without giving different prompts for different errors. I didn’t think much of it before. In general, we believe that the interface should be stable and available, and there should be no errors. However, it cannot be ruled out that there are indeed errors. In this case, friendly and useful error messages can prompt user experience and help to quickly troubleshoot problems.

Generally, in the project, we will do unified interception processing for interface requests, such as adding token in the header of request, and making unified page prompt for errors in response. This time we will focus on how to handle the error when the interface reports an error.

Current treatment

Let’s start with a simplified version of our axios-wrapped unified handling of intercepting interface responses (removing business processing such as login expiration)

const api = axios.create({
  timeout: process.env.VUE_APP_REQUEST_TIMEOUT || 30 * 1000.headers: {}});// Response interception
api.interceptors.response.use((res = {}) = > {
  try {
    const status = res.status;
    if (/^2\d{2}/.test(status)) {
      const data = res && res.data;
      // Error message
      if(data && +data.code ! = =0 && data.message) {
        Message({ // Page prompt
          message: data.message,
          type: 'warning'
        });
        return Promise.reject(data);
      }
      return Promise.resolve(data && data.data); }}catch (e) {
    return Promise.reject(e); }},// Non-2XX state response
e= > {
  if (e && e.response && e.response.status >= 400) {
    // Error message
    if (e.response.data && e.response.data.message) {
      Message({
        message: e.response.data && e.response.data.message,
        type: 'warning'
      });
    } else {
      Message({
        message: 'Network error, please try again later'.type: 'warning'}); }}else {
    Message({
      message: 'Network error, please try again later'.type: 'warning'
    });
  }
  return Promise.reject(e); });Copy the code

Looking at the code above, we can see why our message is “network error, please try again later”. We treat almost all errors except “200” as “network error, please try again later”.

Axios source code for response processing

First we need to figure out how Axios handles response and go directly to the relevant source code.

The axios process calls both resolve and reject at the end. The settle validateStatus method determines resolve or reject. Therefore, if we do not rewrite validateStatus, the 2** status code will be resolve. All the rest is reject.

In addition, in addition to the status returned by the interface request, there are also situations where status does not exist, such as interface timeout, network error, and interface cancellation, as follows:

The optimization results

We can see that there is no problem in the processing of resolve, while there is some problem in the processing of reject. For the 3** state, the processing mode is different from other status codes, and the else logic is directly entered. For the status codes of 4**, 5**, and 1**, there is no direct indication of what the returned status code is.

So let’s change it a little bit, throw message or the current error if status is present, and throw the current error if status is not present. As follows:

// Non-2XX state response
e => {
  if (e && e.response && e.response.status ) {
    Message({
      message: e.response.data && e.response.data.message || e,
      type: 'warning'
    });  
  } else {
    Message({
      message: e,
      type: 'warning'
    });
  }
  return Promise.reject(e);
}

Copy the code

This optimization is simply over!! But we can’t stop learning!

Is the 304 status code being processed as an error?

So let’s go over HTTP status codes and test all of them to see what they actually look like

HTTP status code emulation

The HTTP status code

In fact, many HTTP status codes are rarely encountered, the most common status code is 200,400,404,500,502,503 and so on.

  • 1xx
    • 100 “continue”
    • 101 “switching protocols”
    • 102 “processing”
  • 2xx
    • 200 “ok”
    • 201 “created”
    • 202 “accepted”
    • 203 “non-authoritative information”
    • 204 “no content”
    • 205 “reset content”
    • 206 “partial content”
    • 207 “multi-status”
    • 208 “already reported”
    • 226 “im used”
  • 3xx
    • 300 “multiple choices”
    • 301 “moved permanently”
    • 302 “found”
    • 303 “see other”
    • 304 “not modified”
    • 305 “use proxy”
    • 307 “temporary redirect”
    • 308 “permanent redirect”
  • 4xx
    • 400 “bad request”
    • 401 “unauthorized”
    • 402 “payment required”
    • 403 “forbidden”
    • 404 “not found”
    • 405 “method not allowed”
    • 406 “not acceptable”
    • 407 “proxy authentication required”
    • 408 “request timeout”
    • 409 “conflict”
    • 410 “gone”
    • 411 “length required”
    • 412 “precondition failed”
    • 413 “payload too large”
    • 414 “uri too long”
    • 415 “unsupported media type”
    • 416 “range not satisfiable”
    • 417 “expectation failed”
    • 418 “I’m a teapot”
    • 422 “unprocessable entity”
    • 423 “locked”
    • 424 “failed dependency”
    • 426 “upgrade required”
    • 428 “precondition required”
    • 429 “too many requests”
    • 431 “request header fields too large”
  • 5xx
    • 500 “internal server error”
    • 501 “not implemented”
    • 502 “bad gateway”
    • 503 “service unavailable”
    • 504 “gateway timeout”
    • 505 “http version not supported”
    • 506 “variant also negotiates”
    • 507 “insufficient storage”
    • 508 “loop detected”
    • 510 “not extended”
    • 511 “network authentication required”

So what method can we use to simulate different HTTP status codes? After all, the real backend interface environment is unlikely to have all the status codes, so we need to simulate ourselves. What can we use to simulate?

Postman simulation

The first thing that comes to mind is to use Postman, which has a mock Server feature that allows you to start a mock server locally. Here’s how to do it. It’s very simple. Step one:Step 2: Enter multiple Request urls and Response codes to be simulatedStep 3: Enter the server nameStep 4: Generate Mock urls

We can just use the Mock URL we generated at the end in our project. However, a total of 60 status codes, configuration feel very troublesome ah, lazy people think lazy way, then we still write a server.

Postman is an easy way to test a few status codes.

Koajs simulation

We write the simplest server in KOA, where an interface receives a code parameter and sets it to status as follows

const koa = require('koa');
const koaRouter = require('koa-router' );
const compose = require('koa-compose');
const app = new koa();
const router = new koaRouter();

router.get('/status/test'.async (ctx, next) => {
  ctx.body = 'test'
  ctx.status = +ctx.query.code
  next();
});

const middlewares = compose([
  router.routes()
])

app.use(middlewares);
app.listen(3000);
Copy the code

In addition, we write a simple page, you can enter the status code, as an input parameter to the test interface.

The test results

Test for several common status codes and errors

  • 200

  • 500

  • 404

  • 304

304 is really wrong, that really need to deal with it? And then we look down

  • timeout

  • Broken network

Is it ok to set a status code that is not in the specification?

  • 800

  • 8000

The status must be a number between 100 and 999, otherwise an error will be thrown, so the foreground will return 500 status codes.

Cache: 200 and 304

Interview questions often ask about browser caching, strong caching, and negotiated caching, so let’s try it out.

Strong cache

Strong caching is controlled through the Expires and cache-Control fields.

Expires

Expires is the HTTP1.0 specification and is a time string in THE GMT format.

router.get('/status/test'.async (ctx, next) => {
  ctx.body = 'test'
  ctx.set({
    'Expires':new Date('the 2021-09-16 14:37:30'),
  })
  next();
});
Copy the code

First request: 200 OKFirst request: 200 from Disk cacheAfter the Expires time Expires, the re-request becomes 200 OK again

Cache-Control

Cache-control is an HTTP1.1 specification that sets the Cache time through max-age

Set cache-control on the interface to 60 seconds.

router.get('/status/test'.async (ctx, next) => {
  ctx.body = 'test'
  ctx.set({
    'Cache-Control':'max-age=60'
  })
  next();
});
Copy the code

First request: 200 OKFirst request: 200 from Disk cache60 seconds later the request becomes 200 again

Expires and Cache-control priorities

Cache-control > Expires. When cache-control is present, Expires is ignored

Here’s what happens when Expires and cache-Control are both set:

  • Cache-control earlier than Expires
router.get('/status/test'.async (ctx, next) => {
  ctx.body = 'test'
  //ctx.status = +ctx.query.code,
  ctx.set({
    'Cache-Control':'max-age=20'./ / after 20 seconds
    'Expires':new Date('the 2021-09-16 20:37:30'), // 8 PM, currently 3 PM
  })
  next();
});
Copy the code

The cache expires after 20 seconds

  • The cache-control later than Expires
router.get('/status/test'.async (ctx, next) => {
  ctx.body = 'test'
  //ctx.status = +ctx.query.code,
  ctx.set({
    'Cache-Control':'max-age=300'.// 300 seconds 5 minutes later
    'Expires':new Date('the 2021-09-16 15:02:00'), // It's 3pm now, 2 minutes later
  })
  next();
});
Copy the code

After 3 minutes, the cache still exists, and after 5 minutes, the cache expires

From Memory cache and from Dist cache

Caches are also divided into memory cache and dist cache. The Memeroy cache reads faster than dist cache, but the memory cache is freed after the process ends.

The browser reads the Memory cache first and dist cache second. Not everything that needs to be cached is cached in memory cache. General images, JS and other resource files are cached in both Memory cache and Dist Cache. The cache of interface classes is stored in Dist Cache. Sometimes in memory cache, sometimes in dist cache. (This may involve the underlying caching strategy of Chrome, no research)

Let’s take a look at the following example, we use koa-static to handle static resources, and set cache-control for 60 seconds, and introduce IMG, CSS, and JS to the page to try

const static = require('koa-static');
const middlewares = compose([
  router.routes(),
  static(__dirname + '/static', {setHeaders:(res,path,stats) = >{
      res.setHeader('Cache-Control'.'max-age=60')}})])Copy the code

If you close Chrome and open the dist cache again, the cache will be freed, and the dist cache will be fetched as follows:

Expires and cache-control Cache times are not set

Strong caching is still possible if Expires and cache-Control are not set. The cache time is the Date value minus the last-modified value divided by 10.

Negotiate the cache

When the strong cache fails, the request will be sent to the back end, which will compare and judge the freshness. If the content does not change, 304 will be returned, and the local cache will be read. If the content changes, 200 will be returned

The Etag and if – none – matched

Etag is generally the hash of each file, and corresponding to Etag is if-none-matched. When Etag is matched in the request, the next request will be matched with if-none-matched in the request, whose value is the Etag of the last request, and sent to the server. The server compares the current Etag and if-none-matched, if the same, it indicates that the file is not changed.

The last-modified and if – Modified – since

Last-modify is the time when the file was Last modified. Last Modify corresponds to if-modified-since. If last-modify is included in a request, if-modified-since will be added to the next request. The time is the Last last-modify value, which indicates whether the file has changed according to the comparison between last-modified and if-modified-since

The instance

We continue to modify the original interface, setting Etag and Last-Modified as follows

router.get('/status/test'.async (ctx, next) => {
  ctx.body = 'test'
  ctx.set({ 
    'Etag': '1234'.'Last-Modified': new Date('2021-09-17')})if(ctx.fresh){
    ctx.status = 304}});Copy the code

The server needs to check whether the file has changed to set the status code of 304. Here we use CTx.fresh and KOA uses ctX.freshfreshTo deal with the need for updates, you can look at the source code, as follows

Take a look at the effect:At this time, we found that 304 did not report an error, and we got 200 when we got the results from the page. Why?Because 304 will read the local cache, the local cache is 200, so the final result will be resolve.So let’s test that by adding random to the return

router.get('/status/test'.async (ctx, next) => {
  const num = Math.random();
  ctx.body = num;
  ctx.set({ 
    'Etag': '12341234',})if(ctx.fresh){
    ctx.status=304
  }
  console.log(num,ctx.response)
  next();
});
Copy the code
  • First request

The server returns 200, with the generated random number in the bodyThe browser display is the same as that on the server

  • Second request

The server side returns 304 with body emptyBrowser side interface 304, return value is there, the printed value is the previous result

Therefore, the status code of 304 does not need to be processed separately

Cache summary

Look at the picture directly

Write in the last

A few final thoughts: Rote learning is not practical. ‘!

If you have any questions about the above content, welcome to communicate and correct!