client

package
v0.11.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 3, 2026 License: MIT Imports: 24 Imported by: 5

Documentation

Index

Constants

View Source
const DefaultDNSQueryURL = defaultDNSQueryURL

DefaultDNSQueryURL 是未设置 PIXIV_DNS_QUERY_URL 时使用的默认解析方式:系统解析。

公共 DoH 端点不再适合作为默认值——可用的端点随网络环境变化, 写死其中一个会让不设置该变量的调用者在端点不可达时整体不可用。 系统解析在任何环境下都存在,因此作为默认值;需要绕开被污染的 系统解析时再显式设置本变量。

View Source
const ECHDefaultPublicName = "cloudflare-ech.com"

ECHDefaultPublicName 是施加 ECH 时的外层 SNI。

该名字写在 ECHConfig 的 public_name 字段内,是 Cloudflare 持有证书、 用于完成外层握手的公共名。实测改写该字段为其他名字会握手失败, 因此默认值不应被改动;仅当目标使用其他 ECH 提供方时才需要通过选项指定。

Variables

View Source
var (
	// ErrImageURLNotRecognized 表示入参不是可识别的 pixiv 图片或动图 zip 地址。
	ErrImageURLNotRecognized = errors.New("pixiv: client: 不是可识别的 pixiv 图片或动图 zip 地址")
	// ErrImageRejected 表示服务端以非成功状态码拒绝了这次请求。
	ErrImageRejected = errors.New("pixiv: client: 图片请求被拒绝")
	// ErrImageHostUnreachable 表示主机不可达或无法解析。
	ErrImageHostUnreachable = errors.New("pixiv: client: 图片主机不可达")
)

pixiv: client: FetchImage: 的分类错误,供调用者用 errors.Is 分支处理, 而不必匹配错误文本。三类分别对应「传错了参数」「服务端拒绝了这次请求」 「根本没连上主机(含解析失败)」。

View Source
var Default = New()

Default 客户端,与 New 走同一装配路径。

View Source
var DefaultTransport http.RoundTripper = &AutoTransport{}

DefaultTransport 是新建客户端的默认传输,替换它可在进程级改变新建客户端的传输行为。

View Source
var ErrECHRejected = errors.New("服务端拒绝 ECH 且未提供可用配置")

ErrECHRejected 表示服务端拒绝了 ECH,且未下发可重试的配置。

这是协议中有意义的信号:服务端拒绝但不下发 retry_configs,说明它不接受 ECH, 继续重试没有意义。调用者可用 errors.Is 判定并据此改用其它传输方式。

需要注意该错误无法区分成因——目标主机不在 Cloudflare 之后、服务端不启用 ECH、 或网络中间设备改写了握手,都会走到这里,因为协议本身不携带拒绝原因。 因此错误信息只陈述事实,不断言成因。

Functions

func CheckAPIResponse added in v0.11.1

func CheckAPIResponse(resp *http.Response) error

CheckAPIResponse 校验响应状态,成功返回 nil,非 2xx 返回 ErrAPIRejected。

状态码只有从响应本身才读得到,因此校验必须发生在解析之前:ParseAPIResponseV2 为有 {error, body} 信封的端点两步一起做完,而没有信封的旧式端点 (ranking.php 的 contents 在顶层)用本函数校验后自己读响应体。 不校验的后果是把边缘节点的整页 HTML 当成数据返回给调用方,err == nil, 调用方无从按状态码退避重试,也分不清「被拒绝」与「响应格式不对」。

本函数只判状态:不读也不关响应体,关闭由调用方负责(ParseAPIResponseV2 已代为关闭)。

func NewECHTransport added in v0.8.0

func NewECHTransport(base *http.Transport, opts ...ECHOption) http.RoundTripper

NewECHTransport 返回一个施加 ECH 的传输:在 base 之上叠加 「用加密的 ClientHello 连接」这一种连接能力。

ECH 把 ClientHello 拆成内外两层:外层使用公共的 cloudflare-ech.com 作为 SNI, 内层才是真实主机名且被加密,中间设备只能看到外层名字。因此本传输适用于 **按 SNI 封锁但目标主机托管在 Cloudflare** 的场景。

适用范围

只适用于托管在 Cloudflare 的主机。对不在 Cloudflare 之后的主机(例如 pixiv 自有源站 i.pximg.net),其证书与 ECH 的外层名不匹配,ECH 不适用。

配置来源

两种方式:调用者用 WithECHConfigList 提供,或由本传输自行取得。自行取得 经 TLS 握手自举完成,**不需要 DNS**,也不需要外部文件:先发送一份结构合法但 服务端无法解密的配置,服务端拒绝时会在 HelloRetryRequest 中下发真正的配置。

轮换自愈

配置会轮换,同一时刻新旧配置可能在不同边缘节点并存。服务端无法解密时会下发 retry_configs,本传输用它重试并记住新配置,因此不需要重启或外部定时任务。 服务端明确拒绝且未下发配置时返回错误,不静默退回明文握手。

与代理的关系

ECH 的意义是直连时绕开按 SNI 的封锁,因此本传输的数据连接不走代理; 但代理的来源决定处理方式:

  • base 由本库自建时(AutoTransport 未设置 Base),其代理只来自进程环境 变量,不是调用者的意图,故被忽略,请求直连。常见情形是调用者为了让 DoH 能出网而设了 HTTPS_PROXY。
  • 调用者显式提供 base 时,其中的代理是明确的指令。经代理发出就依赖不了 直连,ECH 无法生效,此时请求返回错误说明该冲突,而不是静默改用普通连接 ——静默降级会让调用者以为 ECH 生效了。

直接构造本传输(NewECHTransport)等同于后者:调用者既然传入了 base, 其中的代理即视为显式指定。

需要 TLS 1.3。本传输不使用 DialTLSContext:标准库文档明确后者只对 non-proxied 请求生效,存在代理时被静默忽略,能力不生效且无任何提示。TLSClientConfig 承载 ECH,直连所需的拨号能力由 base 提供。

返回的传输不改变调用者的 base,因此可与其他原语嵌套组合。

返回类型是 http.RoundTripper 而非 *http.Transport:自行取得配置与配置轮换 都发生在运行时,而 EncryptedClientHelloConfigList 是每 *http.Transport 一份、 无法按请求切换的字段,必须换用一份带新配置的传输才能表达,故以包装型实现。

func NewHostAliasTransport added in v0.9.0

func NewHostAliasTransport(base *http.Transport, hostAlias map[string]string) *http.Transport

NewHostAliasTransport 返回一个在拨号时把目标主机按 hostAlias 映射的底层传输: 请求主机的 Host/SNI 不变,但解析与拨号使用别名后的主机。

用途是「请求一个主机、连接到其源站」这类组合:例如请求 Host:www.pixiv.net, 但经不发送 SNI 的方式连接时落到 pixiv.net 源站的地址(Cloudflare 拒绝 no-SNI 握手,只有源站接受)。它自带经请求上下文注入解析器的解析,因此可被 NewNoSNITransport 等原语组合,别名不必写进那些原语本身。

别名按请求主机精确匹配;未列入的主机照常解析自身。base 为空时使用进程默认传输。

func NewNoSNITransport added in v0.8.0

func NewNoSNITransport(base *http.Transport) *http.Transport

NewNoSNITransport 返回一个不发送 SNI 的传输原语:在 base 之上叠加 「握手时不携带 SNI」这一种连接能力。

它不含主机判断,也不含环境判断:单独使用它的人需要自己决定何时用它。 若需要「按主机自动选用」,用 NewRoutedTransport。

不发送 SNI 后标准库无法自动按主机名校验证书,因此本原语改为自行校验证书链。 由于原语不知道目标主机名,它只校验证书链;主机名校验需要知道请求主机, 由 NewRoutedTransport 在收到响应后补齐。 需要更严格的校验时,调用者可在返回的传输上自行设置 VerifyPeerCertificate。

返回的传输克隆自 base,因此可与其他原语嵌套组合,且不改变调用者的 base。

func NewRoutedTransport added in v0.8.0

func NewRoutedTransport(api, image http.RoundTripper) http.RoundTripper

NewRoutedTransport 按请求主机把请求交给适合该主机的通道。

主机清单由库持有:调用者不需要知道 pixiv 有哪些主机、哪个主机该用哪种方式, 也不必自己维护这份清单。两个通道由调用者提供,因此可以各自带上自己的拨号、 代理与 DNS 设置——API 主机与图片主机需要不同的连接方式,也就需要不同的传输。 本函数只做路由,不代为构造传输;需要自动装配时用 AutoTransport。

image 通道按「不发送 SNI」的传输理解(通常由 NewNoSNITransport 构造):它的 握手不含主机名,标准库因此无法按主机名校验证书,本传输在收到响应后补齐这一步。 api 通道不做此假定,其证书由该传输自行校验。

未列入清单的主机交给 api,即 api 兼作默认通道。

func NoSNIHostTarget added in v0.9.0

func NoSNIHostTarget() map[string]string

NoSNIHostTarget 返回库为不发送 SNI 的连接使用的目标主机别名副本 (www.pixiv.net → pixiv.net 源站)。供诊断工具等复现库的组合方式时读取; 返回副本,调用者修改不影响库。

func ParseAPIResponseV2 added in v0.9.1

func ParseAPIResponseV2(resp *http.Response) (_ json.RawMessage, err error)

ParseAPIResponseV2 校验响应状态并解析 API 响应体,返回信封中 body 部分的原始 JSON。

它是 [ParseAPIResponse] 的替代:状态码只有从响应本身才读得到, 因此由本函数一并校验,调用者不必(也无法)在别处补这一步。

成功状态(2xx)之外的响应以 ErrAPIRejected 报错,错误里带上被拒响应 (状态码在其中,用 errors.As 取回),但不读取响应体——被边缘节点拒绝时响应体常是 整页 HTML,对调用者没有价值。失败路径下响应体已由本函数关闭。

成功状态下仍需是可解析的 JSON 信封:信封里 error 为真时报出其中的 message, error 直接是错误信息字符串时按该字符串报错,否则返回 body 字段的原始 JSON。 204 这类无正文的成功响应按空值处理。

响应体不是 {error, body} 信封的旧式端点(ranking.php)不能走本函数, 那种响应要自己配 CheckAPIResponse 加自己的解析。

func ParseAPIResult deprecated added in v0.2.0

func ParseAPIResult(r io.Reader) (ret gjson.Result, err error)

Deprecated: use ParseAPIResponseV2 instead. ParseAPIResult parses error from json api response, and returns body part.

它同样不接收响应本身,因此也无法校验状态码。

func With added in v0.4.0

func With(ctx context.Context, v *Client) context.Context

With set client to context.

Types

type AutoTransport added in v0.8.0

type AutoTransport struct {
	// Base 提供拨号、代理与 DNS。置空时使用进程默认传输;应在首次使用前设置。
	//
	// 显式设置 Base 表示调用者指定了自己希望的管道,库据此行事:ECH 主机若因
	// 该传输的代理而无法直连,会返回错误而不是悄悄改用普通连接。
	// 未设置时 base 由库自建,其代理仅来自进程环境变量,ECH 路由会忽略它。
	Base http.RoundTripper
	// contains filtered or unexported fields
}

AutoTransport 自动为请求选择最合适的传输方式,尽力而为。

它不承诺内部实现:可能使用路由传输,也可能不使用;可能记住上次成功的 方式,也可能不。调用者不应依赖它的选择过程,只应依赖「请求最终被正确发出, 或返回错误」这一外部结果。

func (*AutoTransport) RoundTrip added in v0.8.0

func (t *AutoTransport) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip implements http.RoundTripper

type Client added in v0.2.0

type Client struct {
	http.Client
	// contains filtered or unexported fields
}

Client to send request to pixiv server.

零值是安全的:它是一个不做任何特殊处理的标准 HTTP 客户端。 要得到本库的默认行为(默认传输、默认 User-Agent、环境变量播种的凭据),用 New。 一个 Client 可被多个 goroutine 并发使用,也可被值拷贝。

func For added in v0.4.0

func For(ctx context.Context) *Client

For get client from context.

func New added in v0.8.0

func New(opts ...Option) *Client

New 依据选项构建客户端,未显式设置的项才由默认值填充。

装配只发生在这一处;构造过程不发起任何网络请求、不返回错误, 因此可用于包级变量初始化,失败在首次使用时以错误呈现。

func (Client) EndpointURL added in v0.2.0

func (c Client) EndpointURL(path string, values *url.Values) *url.URL

EndpointURL returns url for server endpint.

func (*Client) FetchImage added in v0.8.0

func (c *Client) FetchImage(ctx context.Context, imageURL string) (*http.Response, error)

FetchImage 取回图片或动图 zip 的内容,返回可读的响应,由调用者自行消费—— 写文件、解码、计算哈希或流式处理皆可。

它处理调用者无从得知的部分:

动图 zip 地址(img-zip-ugoira 路径段,来自 [FetchUgoiraMeta] 之类元数据接口) 与图片同主机、同样要求 Referer,因此一并由此方法取回。

主机与其接入方式由客户端的传输决定(默认传输会按主机选用合适的方式), 本方法不做这套判断,也不复制一份主机清单。

响应体未被转码或重新压缩,字节与源站返回的一致,因此可据其校验哈希; 内容格式从响应的 Content-Type 读取,不要按 URL 扩展名推断(原图尤甚)。 响应体由调用者负责关闭;失败路径下响应体已由本方法关闭,并从返回值中移除, 因此无需(也无法)再关闭一次。

ctx 被取消时取回中止,错误可用 errors.Is(err, context.Canceled) 辨认。

func (*Client) GetWithContext added in v0.4.0

func (c *Client) GetWithContext(ctx context.Context, url string) (resp *http.Response, err error)

GetWithContext create get request with context and do it.

func (Client) IsLoggedIn added in v0.2.0

func (c Client) IsLoggedIn() (ret bool, err error)

IsLoggedIn checks login status base on `HEAD <server url>/setting_user.php` response status.

func (*Client) SetRequestOptions added in v0.4.1

func (c *Client) SetRequestOptions(options ...RequestOption)

SetRequestOptions for all requests

type ECHOption added in v0.8.0

type ECHOption interface {
	// contains filtered or unexported methods
}

ECHOption 描述构造 ECH 传输原语的一项设置。

与 Option 一样是带未导出方法的接口,从而封闭实现集合, 调用者无法写出本包未预期的选项。

func WithECHConfigList added in v0.8.0

func WithECHConfigList(list []byte) ECHOption

WithECHConfigList 用调用者提供的 ECHConfigList 施加 ECH。

未提供时原语会自行取得配置。配置会轮换,自行提供一份静态配置意味着 配置过期后连接会失败,直到调用者更新它;原语仍会在服务端拒绝时 用下发的 retry_configs 自愈。

func WithECHPublicName added in v0.8.0

func WithECHPublicName(name string) ECHOption

WithECHPublicName 指定 ECH 的外层名(public_name)。

默认值为 ECHDefaultPublicName,即 Cloudflare 的公共名,适用于访问托管在 Cloudflare 的主机。**该值不是可随意改动的参数**:它必须是 ECH 提供方持有 证书的公共名,实测改写为等长的其它名字会导致握手失败,使用其它组织 (如 defo.ie、tls-ech.dev)的配置同样失败。

因此只有目标确实使用其它 ECH 提供方时才需要设置它。

type ErrAPIRejected added in v0.9.1

type ErrAPIRejected struct {
	// Response 是被拒绝的响应,仅供读取元数据。
	Response *http.Response
}

ErrAPIRejected 表示服务端以失败状态码拒绝了这次 API 请求(例如 403、404、429、503), 因此响应体不是本库要解析的 JSON。调用者可用它分辨「被拒绝」与「响应格式不对」, 前者通常值得退避重试或提示重新登录。

这是携带本次请求数据的结构化错误,用 errors.As 取回,不是 errors.Is:

var rej *ErrAPIRejected
if errors.As(err, &rej) {
	log.Println(rej.Response.StatusCode)
}

ParseAPIResponseV2 对非 2xx 状态返回本类型的指针,调用者再包一层错误也能取回。

CheckAPIResponse 同样返回本类型。

Response 是唯一的状态来源:状态码、状态行、请求 URI、响应头都在其中。 其 Body 从不被本库的拒绝路径读取;ParseAPIResponseV2 报错时已关闭它, 直接使用 CheckAPIResponse 的调用方要自己负责关闭。

func (*ErrAPIRejected) Error added in v0.11.0

func (e *ErrAPIRejected) Error() string

Error 实现 [error]。文本只含状态行,不带响应体——被边缘节点拒绝时响应体常是 整页 HTML,对调用者没有价值。

type Option added in v0.8.0

type Option interface {
	// contains filtered or unexported methods
}

Option 描述客户端的一项显式设置。

它是带未导出方法的接口而非函数类型,因此实现集合封闭在本包内, 调用者无法写出本包未预期的选项。

func WithDNSResolver added in v0.8.0

func WithDNSResolver(r dns.Resolver) Option

WithDNSResolver 指定本库自行解析主机名时使用的解析器。

仅在使用本库自带的连接能力(例如图像主机的无 SNI 直连)时生效, 调用者自带的传输自行决定如何解析。 显式传入 nil 表示使用系统解析。

func WithPHPSESSID added in v0.8.0

func WithPHPSESSID(v string) Option

WithPHPSESSID 用 PHPSESSID Cookie 登录。

func WithServerURL added in v0.8.0

func WithServerURL(v string) Option

WithServerURL 指定服务地址,用于测试与镜像场景。

func WithTransport added in v0.8.0

func WithTransport(rt http.RoundTripper) Option

WithTransport 用调用者提供的传输发送请求,优先于 DefaultTransport。

func WithUserAgent added in v0.8.0

func WithUserAgent(v string) Option

WithUserAgent 设置默认 User-Agent 请求头。

type RequestOption added in v0.4.1

type RequestOption = func(req *http.Request)

RequestOption can mutate request before actual send it.

type RequestOptionsTransport added in v0.4.1

type RequestOptionsTransport struct {
	// contains filtered or unexported fields
}

RequestOptionsTransport allow change request before do it.

func (*RequestOptionsTransport) RoundTrip added in v0.4.1

func (t *RequestOptionsTransport) RoundTrip(req *http.Request) (resp *http.Response, err error)

RoundTrip implements http.RoundTripper

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL