mirror of
https://github.com/honojs/hono.git
synced 2024-11-24 02:07:30 +01:00
add tests and fixed logics
This commit is contained in:
parent
8c79f9e5d9
commit
79ebfef6bd
@ -5,7 +5,7 @@ const app = new Hono()
|
|||||||
|
|
||||||
app.use(partialContent())
|
app.use(partialContent())
|
||||||
|
|
||||||
const body = 'Hello World with Partial Content Middleware!'
|
const body = 'This is a test mock data for range requests.'
|
||||||
|
|
||||||
app.get('/hello.jpg', (c) => {
|
app.get('/hello.jpg', (c) => {
|
||||||
return c.body(body, {
|
return c.body(body, {
|
||||||
@ -17,43 +17,158 @@ app.get('/hello.jpg', (c) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('Partial Content Middleware', () => {
|
describe('Partial Content Middleware', () => {
|
||||||
it('Should return 206 response with correct contents', async () => {
|
it('Should return the first 5 bytes of the mock data (bytes=0-4)', async () => {
|
||||||
|
const res = await app.request('/hello.jpg', {
|
||||||
|
headers: {
|
||||||
|
Range: 'bytes=0-4',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(res.headers.get('Content-Type')).toBe('image/jpeg')
|
||||||
|
expect(res.headers.get('Content-Range')).toBe('bytes 0-4/44')
|
||||||
|
expect(await res.text()).toBe('This ')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should return the bytes from 6 to 10 (bytes=6-10)', async () => {
|
||||||
|
const res = await app.request('/hello.jpg', {
|
||||||
|
headers: {
|
||||||
|
Range: 'bytes=6-10',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(res.headers.get('Content-Type')).toBe('image/jpeg')
|
||||||
|
expect(res.headers.get('Content-Range')).toBe('bytes 6-10/44')
|
||||||
|
expect(await res.text()).toBe('s a t')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should return multiple ranges of the mock data (bytes=0-4, 6-10)', async () => {
|
||||||
const res = await app.request('/hello.jpg', {
|
const res = await app.request('/hello.jpg', {
|
||||||
headers: {
|
headers: {
|
||||||
Range: 'bytes=0-4, 6-10',
|
Range: 'bytes=0-4, 6-10',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
expect(res.status).toBe(206)
|
|
||||||
expect(res.headers.get('Content-Type')).toMatch(
|
expect(res.headers.get('Content-Type')).toBe(
|
||||||
/^multipart\/byteranges; boundary=PARTIAL_CONTENT_BOUNDARY$/
|
'multipart/byteranges; boundary=PARTIAL_CONTENT_BOUNDARY'
|
||||||
)
|
)
|
||||||
expect(res.headers.get('Content-Range')).toBeNull()
|
|
||||||
expect(res.headers.get('Content-Length')).toBe('44')
|
const expectedResponse = [
|
||||||
expect(await res.text()).toBe(
|
|
||||||
[
|
|
||||||
'--PARTIAL_CONTENT_BOUNDARY',
|
'--PARTIAL_CONTENT_BOUNDARY',
|
||||||
'Content-Type: image/jpeg',
|
'Content-Type: image/jpeg',
|
||||||
'Content-Range: bytes 0-4/44',
|
'Content-Range: bytes 0-4/44',
|
||||||
'',
|
'',
|
||||||
'Hello',
|
'This ',
|
||||||
'--PARTIAL_CONTENT_BOUNDARY',
|
'--PARTIAL_CONTENT_BOUNDARY',
|
||||||
'Content-Type: image/jpeg',
|
'Content-Type: image/jpeg',
|
||||||
'Content-Range: bytes 6-10/44',
|
'Content-Range: bytes 6-10/44',
|
||||||
'',
|
'',
|
||||||
'World',
|
's a t',
|
||||||
'--PARTIAL_CONTENT_BOUNDARY--',
|
'--PARTIAL_CONTENT_BOUNDARY--',
|
||||||
'',
|
'',
|
||||||
].join('\r\n')
|
].join('\r\n')
|
||||||
)
|
expect(await res.text()).toBe(expectedResponse)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should not return 206 response with invalid Range header', async () => {
|
it('Should return the last 10 bytes of the mock data (bytes=-10)', async () => {
|
||||||
const res = await app.request('/hello.jpg', {
|
const res = await app.request('/hello.jpg', {
|
||||||
headers: {
|
headers: {
|
||||||
Range: 'bytes=INVALID',
|
Range: 'bytes=-10',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
expect(res.headers.get('Content-Type')).toBe('image/jpeg')
|
||||||
|
expect(res.headers.get('Content-Range')).toBe('bytes 34-43/44')
|
||||||
|
expect(await res.text()).toBe(' requests.')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should return the remaining bytes starting from byte 10 (bytes=10-)', async () => {
|
||||||
|
const res = await app.request('/hello.jpg', {
|
||||||
|
headers: {
|
||||||
|
Range: 'bytes=10-',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(res.headers.get('Content-Type')).toBe('image/jpeg')
|
||||||
|
expect(res.headers.get('Content-Range')).toBe('bytes 10-43/44')
|
||||||
|
expect(await res.text()).toBe('test mock data for range requests.')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should return 416 Range Not Satisfiable for excessive number of ranges (11 or more)', async () => {
|
||||||
|
const res = await app.request('/hello.jpg', {
|
||||||
|
headers: {
|
||||||
|
Range: 'bytes=0-0,1-1,2-2,3-3,4-4,5-5,6-6,7-7,8-8,9-9,10-10',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(res.status).toBe(416)
|
||||||
|
expect(res.headers.get('Content-Range')).toBeNull()
|
||||||
|
expect(res.headers.get('Content-Length')).toBe('44')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should return 404 if content is not found', async () => {
|
||||||
|
const app = new Hono()
|
||||||
|
app.use(partialContent())
|
||||||
|
|
||||||
|
app.get('/notfound.jpg', (c) => {
|
||||||
|
return c.notFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = await app.request('/notfound.jpg', {
|
||||||
|
headers: {
|
||||||
|
Range: 'bytes=0-10',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(res.status).toBe(404)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should return full content if Range header is not provided', async () => {
|
||||||
|
const res = await app.request('/hello.jpg')
|
||||||
|
|
||||||
expect(res.status).toBe(200)
|
expect(res.status).toBe(200)
|
||||||
expect(res.headers.get('Content-Type')).toBe('image/jpeg')
|
expect(res.headers.get('Content-Type')).toBe('image/jpeg')
|
||||||
|
expect(await res.text()).toBe(body) // Full body should be returned
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should return full content if Content-Length is missing', async () => {
|
||||||
|
const appWithoutContentLength = new Hono()
|
||||||
|
appWithoutContentLength.use(partialContent())
|
||||||
|
|
||||||
|
appWithoutContentLength.get('/hello.jpg', (c) => {
|
||||||
|
return c.body(body, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'image/jpeg',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = await appWithoutContentLength.request('/hello.jpg', {
|
||||||
|
headers: {
|
||||||
|
Range: 'bytes=0-4',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(res.status).toBe(200)
|
||||||
|
expect(res.headers.get('Content-Type')).toBe('image/jpeg')
|
||||||
|
expect(await res.text()).toBe(body)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should return 204 No Content if body is missing', async () => {
|
||||||
|
const appWithoutBody = new Hono()
|
||||||
|
appWithoutBody.use(partialContent())
|
||||||
|
|
||||||
|
appWithoutBody.get('/empty.jpg', (c) => {
|
||||||
|
return c.body(null, 204)
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = await appWithoutBody.request('/empty.jpg', {
|
||||||
|
headers: {
|
||||||
|
Range: 'bytes=0-4',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(res.status).toBe(204)
|
||||||
|
expect(res.headers.get('Content-Type')).toBeNull()
|
||||||
|
expect(await res.text()).toBe('')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -55,7 +55,7 @@ export const partialContent = (): MiddlewareHandler =>
|
|||||||
const contentLength = c.res.headers.get('Content-Length')
|
const contentLength = c.res.headers.get('Content-Length')
|
||||||
|
|
||||||
if (rangeRequest && contentLength && c.res.body) {
|
if (rangeRequest && contentLength && c.res.body) {
|
||||||
const totalSize = parseInt(contentLength, 10)
|
const totalSize = Number(contentLength)
|
||||||
const offsetSize = totalSize - 1
|
const offsetSize = totalSize - 1
|
||||||
const bodyStream = c.res.body.getReader()
|
const bodyStream = c.res.body.getReader()
|
||||||
|
|
||||||
@ -74,17 +74,26 @@ export const partialContent = (): MiddlewareHandler =>
|
|||||||
]
|
]
|
||||||
|
|
||||||
if (contents.length > 10) {
|
if (contents.length > 10) {
|
||||||
c.header('Content-Length', undefined)
|
c.header('Content-Length', totalSize.toString())
|
||||||
c.header('Content-Range', `bytes bytes */${totalSize}`)
|
|
||||||
c.res = c.body(null, 416)
|
c.res = c.body(null, 416)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentType = c.res.headers.get('Content-Type')
|
const contentType = c.res.headers.get('Content-Type')
|
||||||
|
|
||||||
|
if (contents.length === 1) {
|
||||||
|
const part = contents[0]
|
||||||
|
const contentRange = formatRangeSize(part.start, part.end, totalSize)
|
||||||
|
c.header('Content-Range', contentRange)
|
||||||
|
} else {
|
||||||
|
c.header('Content-Type', `multipart/byteranges; boundary=${PARTIAL_CONTENT_BOUNDARY}`)
|
||||||
|
}
|
||||||
|
|
||||||
const responseBody = new ReadableStream({
|
const responseBody = new ReadableStream({
|
||||||
async start(controller) {
|
async start(controller) {
|
||||||
const encoder = new TextEncoder()
|
const encoder = new TextEncoder()
|
||||||
const { done, value } = await bodyStream.read()
|
const { done, value } = await bodyStream.read()
|
||||||
|
|
||||||
if (done || !value) {
|
if (done || !value) {
|
||||||
controller.close()
|
controller.close()
|
||||||
return
|
return
|
||||||
@ -92,26 +101,31 @@ export const partialContent = (): MiddlewareHandler =>
|
|||||||
|
|
||||||
for (const part of contents) {
|
for (const part of contents) {
|
||||||
const contentRange = formatRangeSize(part.start, part.end, totalSize)
|
const contentRange = formatRangeSize(part.start, part.end, totalSize)
|
||||||
|
const sliceStart = part.start
|
||||||
|
const sliceEnd = part.end + 1
|
||||||
|
const chunk = value.subarray(sliceStart, sliceEnd)
|
||||||
|
|
||||||
|
if (contents.length === 1) {
|
||||||
|
controller.enqueue(chunk)
|
||||||
|
} else {
|
||||||
controller.enqueue(
|
controller.enqueue(
|
||||||
encoder.encode(
|
encoder.encode(
|
||||||
`--${PARTIAL_CONTENT_BOUNDARY}\r\nContent-Type: ${contentType}\r\nContent-Range: ${contentRange}\r\n\r\n`
|
`--${PARTIAL_CONTENT_BOUNDARY}\r\nContent-Type: ${contentType}\r\nContent-Range: ${contentRange}\r\n\r\n`
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const sliceStart = part.start
|
|
||||||
const sliceEnd = part.end + 1
|
|
||||||
|
|
||||||
const chunk = value.subarray(sliceStart, sliceEnd)
|
|
||||||
controller.enqueue(chunk)
|
controller.enqueue(chunk)
|
||||||
controller.enqueue(encoder.encode('\r\n'))
|
controller.enqueue(encoder.encode('\r\n'))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contents.length !== 1) {
|
||||||
controller.enqueue(encoder.encode(`--${PARTIAL_CONTENT_BOUNDARY}--\r\n`))
|
controller.enqueue(encoder.encode(`--${PARTIAL_CONTENT_BOUNDARY}--\r\n`))
|
||||||
|
}
|
||||||
|
|
||||||
controller.close()
|
controller.close()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
c.header('Content-Type', `multipart/byteranges; boundary=${PARTIAL_CONTENT_BOUNDARY}`)
|
|
||||||
c.res = c.body(responseBody, 206)
|
c.res = c.body(responseBody, 206)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user