Skip to content

Commit 719c962

Browse files
committed
replace fetch with http.request
1 parent 017b159 commit 719c962

File tree

9 files changed

+138
-140
lines changed

9 files changed

+138
-140
lines changed

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
# Changelog
22

3-
<!--## 🥚 ⟩ [Unreleased]-->
3+
## 🥚 ⟩ [Unreleased]
4+
5+
### Misc. Improvements
6+
- When installing the module, any proxy server defined via `npm config set proxy` or an `HTTPS_PROXY` environment variable will be used to fetch the prebuilt binary
7+
- Replaced `fetch` altogether, now using Node's built-in `http` and `https` modules for better backward compatibility and support for additional [request parameters][request_opts] for [loadImage()][loadImage()]
8+
9+
[request_opts]: https://nodejs.org/api/http.html#httprequestoptions-callback
410

511
## 📦 ⟩ [v3.0.3] ⟩ Aug 20, 2025
612

docs/api/image.md

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -199,11 +199,28 @@ Note that you can pass a wide variety of image sources to the `loadImage` helper
199199

200200
#### Loading URLs
201201

202-
When loading a URL over HTTP(S) you may also include a second argument containing [request options][fetch_opts] to be used when the library calls [`fetch`][fetch] behind the scenes. This can be useful for accessing resources requiring authentication…
202+
When loading a URL over HTTP(S) you may also include a second argument containing options to be used when the library makes its web request behind the scenes.
203203

204+
The supported options include:
205+
- `method`: the http ‘verb’ to use (defaults to `GET`)
206+
- `headers`: an object mapping request header names to values
207+
- `body`: a string or buffer to be sent to the server if the method is set to `POST` or `PUT`
208+
- `auth`: credentials for Basic Auth in the format `"user:password"` which will be used to construct the `Authorization` header (if not supplied)
209+
- `signal`: an [AbortSignal][AbortSignal] to allow the request to be cancelled
210+
- `agent`: an [http.Agent][http_agent] used to customize the connection or `false` to disable the [default agent](https://www.npmjs.com/package/https-proxy-agent) which uses the url in the `HTTP_PROXY` environment variable (if defined) as a proxy server
211+
- any other option supported by Node’s [http.request][http_request] call
212+
213+
214+
These options can be useful for accessing resources requiring authentication…
215+
216+
```js
217+
let img = await loadImage('https://example.com/lightly-protected.png', {
218+
auth: "username:password" // credentials for http-basic-auth
219+
})
220+
```
204221
```js
205222
let img = await loadImage('https://example.com/protected.png', {
206-
headers: {"Authorization": 'Bearer <secret-token-value>'}
223+
headers: {"Authorization": 'Bearer <secret-token-value>'} // token-based auth
207224
})
208225
```
209226

@@ -221,6 +238,8 @@ let img = await loadImage('https://example.com/customized.svg', {
221238
})
222239
```
223240

241+
242+
224243
<!-- references_begin -->
225244
[loadimage]: #loadimage
226245
[img_bind]: #on--off--once
@@ -235,8 +254,9 @@ let img = await loadImage('https://example.com/customized.svg', {
235254
[Buffer]: https://nodejs.org/api/buffer.html
236255
[sharp]: https://sharp.pixelplumbing.com
237256
[sharp_npm]: https://www.npmjs.com/package/sharp
238-
[fetch]: https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch
239-
[fetch_opts]: https://developer.mozilla.org/en-US/docs/Web/API/RequestInit
257+
[http_agent]: https://nodejs.org/api/http.html#class-httpagent
258+
[http_request]: https://nodejs.org/api/http.html#httprequestoptions-callback
259+
[AbortSignal]: https://developer.mozilla.org/en-US/docs/Webhttps://developer.mozilla.org/en-US/docs/Web/API/AbortSignal
240260
[Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
241261
[DataURL]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
242262
[img_element]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement

docs/api/imagedata.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ let id = await loadImageData('some-image-file.raw', 64, 64, {
177177
```
178178

179179
#### Loading URLs
180-
If the `src` argument is a URL, you can optionally include any [request options][fetch_opts] supported by [`fetch`][fetch] in the final argument:
180+
If the `src` argument is a URL, you can optionally include any [supported request options][loadimage_options] in the final argument:
181181
```js
182182
let id = await loadImageData('https://skia-canvas.org/customized.raw', 64, 64, {
183183
colorType: "rgb",
@@ -220,11 +220,10 @@ await loadImageData(sharpImage)
220220
[imgdata_colortype]: #colortype
221221
[imgdata_bpp]: #bytesperpixel
222222
[imgdata_tosharp]: #tosharp
223+
[loadimage_options]: image.md#loading-urls
223224
[skia_colortype]: https://rust-skia.github.io/doc/skia_safe/enum.ColorType.html
224225
[sharp]: https://sharp.pixelplumbing.com
225226
[sharp_npm]: https://www.npmjs.com/package/sharp
226-
[fetch]: https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch
227-
[fetch_opts]: https://developer.mozilla.org/en-US/docs/Web/API/RequestInit
228227
[ImageData]: https://developer.mozilla.org/en-US/docs/Web/API/ImageData
229228
[mdn_ImageData_data]: https://developer.mozilla.org/en-US/docs/Web/API/ImageData/data
230229
[mdn_ImageData_colorspace]: https://developer.mozilla.org/en-US/docs/Web/API/ImageData/colorSpace

lib/classes/imagery.js

Lines changed: 7 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55
"use strict"
66

77
const {RustClass, core, readOnly, inspect, neon, argc, REPR} = require('./neon'),
8-
{fetch} = require('../fetch'),
8+
{fetchURL, decodeDataURL, expandURL} = require('../urls'),
99
{EventEmitter} = require('events'),
10-
{readFile} = require('fs/promises'),
11-
url = require('url')
10+
{readFile} = require('fs/promises')
1211

1312
//
1413
// Image
@@ -35,7 +34,7 @@ class Image extends RustClass {
3534
constructor(data, src='') {
3635
super(Image).alloc()
3736

38-
data = expandURLs(data)
37+
data = expandURL(data)
3938
this.prop("src", ''+src || '::Buffer::')
4039

4140
if (Buffer.isBuffer(data)) {
@@ -94,7 +93,7 @@ class Image extends RustClass {
9493
}
9594
}
9695

97-
src = expandURLs(src)
96+
src = expandURL(src)
9897
this.prop("src", typeof src=='string' ? src : '')
9998

10099
fetchData(src, undefined, loaded, failed)
@@ -221,41 +220,8 @@ function isSharpImage(obj){
221220
}
222221
}
223222

224-
const decodeDataURL = (dataURL, ok, fail) => {
225-
if (typeof dataURL!='string') return fail(TypeError(`Expected a data URL string (got ${typeof dataURL})`))
226-
let [header, mime, enc] = dataURL.slice(0, 40).match(/^\s*data:(?<mime>[^;]*);(?:charset=)?(?<enc>[^,]*),/) || []
227-
if (!mime || !enc) return fail(TypeError(`Expected a valid data URL string (got: "${dataURL}")`))
228-
229-
// SVGs in particular may not be base64 encoded
230-
let content = dataURL.slice(header.length)
231-
if (enc.toLowerCase() != 'base64') content = decodeURIComponent(content)
232-
233-
try{ ok(Buffer.from(content, enc)) }
234-
catch(e){ fail(e) }
235-
}
236-
237-
const fetchURL = (url, fetchOpts, ok, fail) => {
238-
fetch(url, fetchOpts)
239-
.then(resp => {
240-
if (resp.ok) return resp.arrayBuffer()
241-
else throw new Error(`Failed to load image from "${url}" (HTTP error ${resp.status})`)
242-
})
243-
.then(buf => ok(Buffer.from(buf)))
244-
.catch(err => fail(err))
245-
}
246-
247-
const expandURLs = (src) => {
248-
// convert URLs to strings, otherwise pass arg through unmodified
249-
if (src instanceof URL){
250-
if (src.protocol=='file:') src = url.fileURLToPath(src)
251-
else if (src.protocol.match(/^(https?|data):/)) src = src.href
252-
else throw Error(`Unsupported protocol: ${src.protocol.replace(':', '')}`)
253-
}
254-
return src
255-
}
256-
257-
const fetchData = (src, fetchOpts, loaded, failed) => {
258-
src = expandURLs(src)
223+
const fetchData = (src, reqOpts, loaded, failed) => {
224+
src = expandURL(src)
259225
if (Buffer.isBuffer(src)) {
260226
loaded(src, '::Buffer::')
261227
}else if (isSharpImage(src)){
@@ -272,7 +238,7 @@ const fetchData = (src, fetchOpts, loaded, failed) => {
272238
err => failed(err),
273239
)
274240
}else if (/^\s*https?:\/\//.test(src)){
275-
fetchURL(src, fetchOpts,
241+
fetchURL(src, reqOpts,
276242
buffer => loaded(buffer, src),
277243
err => failed(err)
278244
)

lib/fetch.js

Lines changed: 0 additions & 4 deletions
This file was deleted.

lib/urls.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
const url = require('url'),
2+
{http, https} = require('follow-redirects'),
3+
{HttpsProxyAgent} = require('https-proxy-agent')
4+
5+
const UA = {"User-Agent": "Skia Canvas"}
6+
const PROXY_URL =
7+
process.env.https_proxy || process.env.HTTPS_PROXY ||
8+
process.env.http_proxy || process.env.HTTP_PROXY
9+
10+
const fetchURL = (url, opts, ok, fail) => {
11+
let proto = url.slice(0,5).split(':')[0],
12+
client = {http, https}[proto.toLowerCase()]
13+
14+
if (!client){
15+
fail(new Error(`Unsupported protocol: expected 'http' or 'https' (got: ${proto})`))
16+
}else{
17+
opts = opts || {}
18+
opts.headers = {...UA, ...opts.headers}
19+
opts.agent = opts.agent===undefined && PROXY_URL ? new HttpsProxyAgent(PROXY_URL) : opts.agent
20+
21+
let req = client.request(url, opts, resp => {
22+
if (resp.statusCode < 200 || resp.statusCode >= 300){
23+
fail(new Error(`Failed to load image from "${url}" (HTTP error ${resp.statusCode})`))
24+
}else{
25+
const chunks = []
26+
resp.on("data", chunk => chunks.push(chunk))
27+
resp.on("end", () => ok(Buffer.concat(chunks)))
28+
resp.on('error', e => fail(e))
29+
}
30+
})
31+
32+
req.on('error', e => fail(e))
33+
if (opts.body) req.write(opts.body)
34+
req.end()
35+
}
36+
}
37+
38+
const decodeDataURL = (dataURL, ok, fail) => {
39+
if (typeof dataURL!='string') return fail(TypeError(`Expected a data URL string (got ${typeof dataURL})`))
40+
let [header, mime, enc] = dataURL.slice(0, 40).match(/^\s*data:(?<mime>[^;]*);(?:charset=)?(?<enc>[^,]*),/) || []
41+
if (!mime || !enc) return fail(TypeError(`Expected a valid data URL string (got: "${dataURL}")`))
42+
43+
// SVGs in particular may not be base64 encoded
44+
let content = dataURL.slice(header.length)
45+
if (enc.toLowerCase() != 'base64') content = decodeURIComponent(content)
46+
47+
try{ ok(Buffer.from(content, enc)) }
48+
catch(e){ fail(e) }
49+
}
50+
51+
const expandURL = (src) => {
52+
// convert URLs to strings, otherwise pass arg through unmodified
53+
if (src instanceof URL){
54+
if (src.protocol=='file:') src = url.fileURLToPath(src)
55+
else if (src.protocol.match(/^(https?|data):/)) src = src.href
56+
else throw Error(`Unsupported protocol: ${src.protocol.replace(':', '')}`)
57+
}
58+
return src
59+
}
60+
61+
module.exports = {fetchURL, decodeDataURL, expandURL}

0 commit comments

Comments
 (0)