1
0
Fork 0
mirror of https://github.com/terribleplan/next.js.git synced 2024-01-19 02:48:18 +00:00

Implement ETag support for server rendered pages. (#1693)

This commit is contained in:
Arunoda Susiripala 2017-04-18 21:15:42 +05:30 committed by GitHub
parent e0f71d8448
commit f82e52935d
6 changed files with 42 additions and 20 deletions

View file

@ -61,8 +61,10 @@
"case-sensitive-paths-webpack-plugin": "2.0.0", "case-sensitive-paths-webpack-plugin": "2.0.0",
"cross-spawn": "5.1.0", "cross-spawn": "5.1.0",
"del": "2.2.2", "del": "2.2.2",
"etag": "1.8.0",
"fresh": "0.5.0",
"friendly-errors-webpack-plugin": "1.5.0", "friendly-errors-webpack-plugin": "1.5.0",
"glob": "^7.1.1", "glob": "7.1.1",
"glob-promise": "3.1.0", "glob-promise": "3.1.0",
"htmlescape": "1.1.1", "htmlescape": "1.1.1",
"http-status": "1.0.1", "http-status": "1.0.1",

View file

@ -225,7 +225,7 @@ export default class Server {
res.setHeader('X-Powered-By', `Next.js ${pkg.version}`) res.setHeader('X-Powered-By', `Next.js ${pkg.version}`)
} }
const html = await this.renderToHTML(req, res, pathname, query) const html = await this.renderToHTML(req, res, pathname, query)
return sendHTML(res, html, req.method) return sendHTML(req, res, html, req.method)
} }
async renderToHTML (req, res, pathname, query) { async renderToHTML (req, res, pathname, query) {
@ -253,7 +253,7 @@ export default class Server {
async renderError (err, req, res, pathname, query) { async renderError (err, req, res, pathname, query) {
const html = await this.renderErrorToHTML(err, req, res, pathname, query) const html = await this.renderErrorToHTML(err, req, res, pathname, query)
return sendHTML(res, html, req.method) return sendHTML(req, res, html, req.method)
} }
async renderErrorToHTML (err, req, res, pathname, query) { async renderErrorToHTML (err, req, res, pathname, query) {

View file

@ -2,6 +2,8 @@ import { join } from 'path'
import { createElement } from 'react' import { createElement } from 'react'
import { renderToString, renderToStaticMarkup } from 'react-dom/server' import { renderToString, renderToStaticMarkup } from 'react-dom/server'
import send from 'send' import send from 'send'
import generateETag from 'etag'
import fresh from 'fresh'
import requireModule from './require' import requireModule from './require'
import getConfig from './config' import getConfig from './config'
import resolvePath from './resolve' import resolvePath from './resolve'
@ -13,7 +15,7 @@ import ErrorDebug from '../lib/error-debug'
export async function render (req, res, pathname, query, opts) { export async function render (req, res, pathname, query, opts) {
const html = await renderToHTML(req, res, pathname, opts) const html = await renderToHTML(req, res, pathname, opts)
sendHTML(res, html, req.method) sendHTML(req, res, html, req.method)
} }
export function renderToHTML (req, res, pathname, query, opts) { export function renderToHTML (req, res, pathname, query, opts) {
@ -22,7 +24,7 @@ export function renderToHTML (req, res, pathname, query, opts) {
export async function renderError (err, req, res, pathname, query, opts) { export async function renderError (err, req, res, pathname, query, opts) {
const html = await renderErrorToHTML(err, req, res, query, opts) const html = await renderErrorToHTML(err, req, res, query, opts)
sendHTML(res, html, req.method) sendHTML(req, res, html, req.method)
} }
export function renderErrorToHTML (err, req, res, pathname, query, opts = {}) { export function renderErrorToHTML (err, req, res, pathname, query, opts = {}) {
@ -148,9 +150,17 @@ export async function renderScriptError (req, res, page, error, customFields, op
`) `)
} }
export function sendHTML (res, html, method) { export function sendHTML (req, res, html, method) {
if (res.finished) return if (res.finished) return
const etag = generateETag(html)
if (fresh(req.headers, { etag })) {
res.statusCode = 304
res.end()
return
}
res.setHeader('ETag', etag)
res.setHeader('Content-Type', 'text/html') res.setHeader('Content-Type', 'text/html')
res.setHeader('Content-Length', Buffer.byteLength(html)) res.setHeader('Content-Length', Buffer.byteLength(html))
res.end(method === 'HEAD' ? null : html) res.end(method === 'HEAD' ? null : html)

View file

@ -1,4 +1,5 @@
/* global describe, test, expect */ /* global describe, test, expect */
import fetch from 'node-fetch'
export default function (context) { export default function (context) {
describe('Misc', () => { describe('Misc', () => {
@ -12,5 +13,14 @@ export default function (context) {
const html = await context.app.renderToHTML({}, res, '/finish-response', {}) const html = await context.app.renderToHTML({}, res, '/finish-response', {})
expect(html).toBeFalsy() expect(html).toBeFalsy()
}) })
test('allow etag header support', async () => {
const url = `http://localhost:${context.appPort}/stateless`
const etag = (await fetch(url)).headers.get('ETag')
const headers = { 'If-None-Match': etag }
const res2 = await fetch(url, { headers })
expect(res2.status).toBe(304)
})
}) })
} }

View file

@ -4,7 +4,7 @@ import { pkg } from 'next-test-utils'
export default function ({ app }) { export default function ({ app }) {
describe('X-Powered-By header', () => { describe('X-Powered-By header', () => {
test('set it by default', async () => { test('set it by default', async () => {
const req = { url: '/stateless' } const req = { url: '/stateless', headers: {} }
const headers = {} const headers = {}
const res = { const res = {
setHeader (key, value) { setHeader (key, value) {
@ -18,7 +18,7 @@ export default function ({ app }) {
}) })
test('do not set it when poweredByHeader==false', async () => { test('do not set it when poweredByHeader==false', async () => {
const req = { url: '/stateless' } const req = { url: '/stateless', headers: {} }
const originalConfigValue = app.config.poweredByHeader const originalConfigValue = app.config.poweredByHeader
app.config.poweredByHeader = false app.config.poweredByHeader = false
const res = { const res = {

View file

@ -1972,7 +1972,7 @@ esutils@^2.0.0, esutils@^2.0.2:
version "2.0.2" version "2.0.2"
resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
etag@~1.8.0: etag@1.8.0, etag@~1.8.0:
version "1.8.0" version "1.8.0"
resolved "https://registry.npmjs.org/etag/-/etag-1.8.0.tgz#6f631aef336d6c46362b51764044ce216be3c051" resolved "https://registry.npmjs.org/etag/-/etag-1.8.0.tgz#6f631aef336d6c46362b51764044ce216be3c051"
@ -2331,17 +2331,7 @@ glob-promise@3.1.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.npmjs.org/glob-promise/-/glob-promise-3.1.0.tgz#198882a3817be7dc2c55f92623aa9e7b3f82d1eb" resolved "https://registry.npmjs.org/glob-promise/-/glob-promise-3.1.0.tgz#198882a3817be7dc2c55f92623aa9e7b3f82d1eb"
glob@^6.0.1: glob@7.1.1, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1:
version "6.0.4"
resolved "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22"
dependencies:
inflight "^1.0.4"
inherits "2"
minimatch "2 || 3"
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1:
version "7.1.1" version "7.1.1"
resolved "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" resolved "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
dependencies: dependencies:
@ -2352,6 +2342,16 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1:
once "^1.3.0" once "^1.3.0"
path-is-absolute "^1.0.0" path-is-absolute "^1.0.0"
glob@^6.0.1:
version "6.0.4"
resolved "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22"
dependencies:
inflight "^1.0.4"
inherits "2"
minimatch "2 || 3"
once "^1.3.0"
path-is-absolute "^1.0.0"
global@^4.3.0: global@^4.3.0:
version "4.3.1" version "4.3.1"
resolved "https://registry.npmjs.org/global/-/global-4.3.1.tgz#5f757908c7cbabce54f386ae440e11e26b7916df" resolved "https://registry.npmjs.org/global/-/global-4.3.1.tgz#5f757908c7cbabce54f386ae440e11e26b7916df"