• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OtterJS / otterhttp / 10563052266

26 Aug 2024 03:58PM UTC coverage: 84.998%. Remained the same
10563052266

push

github

Lordfirespeed
tooling(style): set `trailingCommas` to `all` and `quoteStyle` to `double`

903 of 1145 branches covered (78.86%)

Branch coverage included in aggregate %.

278 of 297 new or added lines in 56 files covered. (93.6%)

1 existing line in 1 file now uncovered.

1420 of 1588 relevant lines covered (89.42%)

82.34 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.31
/packages/send/src/sendFile.ts
1
import { createReadStream, statSync } from "node:fs"
2
import { extname, isAbsolute } from "node:path"
3
import { join } from "node:path"
4
import { Writable } from "node:stream"
5
import { pipeline } from "node:stream/promises"
6
import mime from "mime"
7

8
import type { HasIncomingHeaders, HasOutgoingHeaders, HasReq, HasStatus, HasWriteMethods } from "./types"
9
import { createETag } from "./utils"
10

11
export type ReadStreamOptions = Partial<{
12
  flags: string
13
  encoding: BufferEncoding
14
  fd: number
15
  mode: number
16
  autoClose: boolean
17
  emitClose: boolean
18
  start: number
19
  end: number
20
  highWaterMark: number
21
}>
22

23
export type SendFileOptions = ReadStreamOptions &
24
  Partial<{
25
    root: string
26
    headers: Record<string, any>
27
    caching: Partial<{
28
      maxAge: number
29
      immutable: boolean
30
    }>
31
  }>
32

33
export type Caching = Partial<{
34
  maxAge: number
35
  immutable: boolean
36
}>
37

38
type SendFileResponse = HasOutgoingHeaders &
39
  HasReq<HasIncomingHeaders> &
40
  HasStatus &
41
  HasWriteMethods &
42
  NodeJS.WritableStream
43

44
export const enableCaching = (res: HasOutgoingHeaders, caching: Caching): void => {
10✔
45
  let cc = caching.maxAge != null && `public,max-age=${caching.maxAge}`
×
NEW
46
  if (cc && caching.immutable) cc += ",immutable"
×
NEW
47
  else if (cc && caching.maxAge === 0) cc += ",must-revalidate"
×
48

NEW
49
  if (cc) res.setHeader("Cache-Control", cc)
×
50
}
51

52
const makeIndestructible = (stream: NodeJS.WritableStream) => {
10✔
53
  return new Writable({ write: stream.write.bind(stream) })
13✔
54
}
55

56
/**
57
 * Sends a file by piping a stream to response.
58
 *
59
 * It also checks for extension to set a proper `Content-Type` header.
60
 *
61
 * Path argument must be absolute. To use a relative path, specify the `root` option first.
62
 *
63
 * @param res Response
64
 * @param path
65
 * @param opts
66
 */
67
export async function sendFile(res: SendFileResponse, path: string, opts: SendFileOptions = {}): Promise<void> {
4✔
68
  const { root, headers = {}, encoding = "utf-8", caching, ...options } = opts
15✔
69
  const req = res.req
15✔
70

71
  if (!isAbsolute(path) && !root) throw new TypeError("path must be absolute")
15✔
72

73
  if (caching) enableCaching(res, caching)
14!
74

75
  const filePath = root ? join(root, path) : path
14✔
76

77
  const stats = statSync(filePath)
14✔
78

79
  headers["Content-Encoding"] = encoding
14✔
80

81
  headers["Last-Modified"] = stats.mtime.toUTCString()
14✔
82

83
  headers.ETag = createETag(stats, encoding)
14✔
84

85
  // TODO: add freshness check here
86

87
  if (!res.getHeader("Content-Type")) {
14✔
88
    const inferredType = mime.getType(extname(path))
13✔
89
    if (inferredType != null) headers["Content-Type"] = inferredType
13✔
90
  }
91

92
  let status = res.statusCode || 200
14!
93

94
  if (req.headers.range) {
14✔
95
    status = 206
2✔
96
    const [x, y] = req.headers.range.replace("bytes=", "").split("-")
2✔
97
    const end = (options.end = Number.parseInt(y, 10) || stats.size - 1)
2!
98
    const start = (options.start = Number.parseInt(x, 10) || 0)
2✔
99

100
    if (start >= stats.size || end >= stats.size) {
2✔
101
      res.setHeader("Content-Range", `bytes */${stats.size}`)
1✔
102
      res.writeHead(416)
1✔
103
      res.end()
1✔
104
      return
1✔
105
    }
106
    headers["Content-Range"] = `bytes ${start}-${end}/${stats.size}`
1✔
107
    headers["Content-Length"] = end - start + 1
1✔
108
    headers["Accept-Ranges"] = "bytes"
1✔
109
  } else {
110
    headers["Content-Length"] = stats.size
12✔
111
  }
112

113
  for (const [key, value] of Object.entries(headers)) {
13✔
114
    if (value == null) continue
73!
115
    res.setHeader(key, value)
73✔
116
  }
117

118
  res.writeHead(status)
13✔
119

120
  const stream = createReadStream(filePath, options)
13✔
121
  await pipeline(stream, makeIndestructible(res))
13✔
122
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc