提交 f82e5293 编写于 作者: A Arunoda Susiripala 提交者: GitHub

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

上级 e0f71d84
...@@ -225,7 +225,7 @@ export default class Server { ...@@ -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 { ...@@ -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) {
......
...@@ -2,6 +2,8 @@ import { join } from 'path' ...@@ -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' ...@@ -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) { ...@@ -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 ...@@ -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)
......
/* 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) { ...@@ -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)
})
}) })
} }
...@@ -4,7 +4,7 @@ import { pkg } from 'next-test-utils' ...@@ -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 }) { ...@@ -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 = {
......
...@@ -1972,7 +1972,7 @@ esutils@^2.0.0, esutils@^2.0.2: ...@@ -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,24 +2331,24 @@ glob-promise@3.1.0: ...@@ -2331,24 +2331,24 @@ 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" version "7.1.1"
resolved "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" resolved "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
dependencies: dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4" inflight "^1.0.4"
inherits "2" inherits "2"
minimatch "2 || 3" minimatch "^3.0.2"
once "^1.3.0" once "^1.3.0"
path-is-absolute "^1.0.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: glob@^6.0.1:
version "7.1.1" version "6.0.4"
resolved "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" resolved "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22"
dependencies: dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4" inflight "^1.0.4"
inherits "2" inherits "2"
minimatch "^3.0.2" minimatch "2 || 3"
once "^1.3.0" once "^1.3.0"
path-is-absolute "^1.0.0" path-is-absolute "^1.0.0"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册