调用GitHub Gist API出现422错误的排查及解决方案
调用GitHub API创建Gist时遇到422错误的排查与解决
问题背景
调用GitHub API将JSON文档存储为Gist,严格遵循API规范实现后收到422错误。该错误表示请求格式合法但服务器无法处理,已确认:
- Auth token有效(否则会返回权限相关错误)
- JSON格式通过验证,且用工具调用API可成功创建Gist
- 请求头按文档推荐设置
- 未触发限流
但代码实现始终返回422错误,文档说明该错误表示“验证失败,或端点被滥用”,未滥用服务,需排查具体原因。
初始报错代码
struct GistPayload : Codable { var Public : Bool var description : String var files : Dictionary<String, Dictionary<String, ProjectExport>> } @objc func exportProject() { guard let projectExport = userProject?.export() else { return } let src = GistPayload(Public: true, description: "testing Gist upload", files: ["testGist" : ["content" : projectExport]]) sendToGist(payload: src) } func encode(src : GistPayload) -> String? { let encoder = JSONEncoder() do { let data = try encoder.encode(src) return String(data: data, encoding: .utf8)! } catch {print("problem \(error)")} return nil } func sendToGist(payload : GistPayload) { guard var data = encode(src: payload) else { print("no data") return } data = data.replacingOccurrences(of: "Public", with: "public") let url = URL(string: "https://api.github.com/gists")! var request = URLRequest(url: url) request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") request.setValue("application/vnd.github+json", forHTTPHeaderField: "Accept") request.setValue("Bearer \(StartController.token)", forHTTPHeaderField: "Authorization") request.httpMethod = "POST" request.httpBody = data.data(using: String.Encoding.utf8) let task = URLSession.shared.dataTask(with: request) { data, response, error in guard let data = data, let response = response as? HTTPURLResponse, error == nil else { print("error", error ?? URLError(.badServerResponse)) return } guard (200 ... 299) ~= response.statusCode else { print("statusCode should be 2xx, but is \(response.statusCode)") print("response = \(response)") return } do { print("done") } catch { print(error) // parsing error if let responseString = String(data: data, encoding: .utf8) { print("responseString = \(responseString)") } else { print("unable to parse response as string") } } } task.resume() }
更新后的代码仍报错
调整结构后代码如下,但仍返回422错误:
struct GistData { var filename : String var description : String var publc : Bool var payload : ProjectExport func files() -> Dictionary<String, Dictionary<String, ProjectExport>> { var rslt : Dictionary<String, Dictionary<String, ProjectExport>> return [filename : ["content" : payload]] } func output() -> Output { return Output(description: description, Public: publc, files: files()) } struct Output : Codable { var description : String var Public : Bool var files : Dictionary<String, Dictionary<String, ProjectExport>> } } @objc func exportProject() { guard let projectExport = userProject?.export() else { return } var gistData = GistData(filename: "newThingie.json", description: "a thing", publc: true, payload: projectExport) sendToGist(payload: gistData.output()) } func encode(src : GistData.Output) -> String? { let encoder = JSONEncoder() do { let data = try encoder.encode(src) return String(data: data, encoding: .utf8)! } catch {print("problem \(error)")} return nil }
错误响应:
statusCode should be 2xx, but is 422
响应详情:
response = <NSHTTPURLResponse: 0x600001d48d40> { URL: https://api.github.com/gists } { Status Code: 422, Headers { "Access-Control-Allow-Origin" = ( "*" ); "Content-Length" = ( 38981 ); "Content-Type" = ( "application/json; charset=utf-8" ); Date = ( "Tue, 22 Nov 2022 04:10:48 GMT" ); Server = ( "GitHub.com" ); "Strict-Transport-Security" = ( "max-age=31536000; includeSubdomains; preload" ); Vary = ( "Accept-Encoding, Accept, X-Requested-With" ); "access-control-expose-headers" = ( "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset" ); "content-security-policy" = ( "default-src 'none'" ); "github-authentication-token-expiration" = ( "2023-02-20 03:06:05 UTC" ); "referrer-policy" = ( "origin-when-cross-origin, strict-origin-when-cross-origin" ); "x-accepted-oauth-scopes" = ( "" ); "x-content-type-options" = ( nosniff ); "x-frame-options" = ( deny ); "x-github-media-type" = ( "github.v3; format=json" ); "x-github-request-id" = ( "D032:5EB9:94899C:9949C2:637C4BC8" ); "x-oauth-scopes" = ( gist ); "x-ratelimit-limit" = ( 5000 ); "x-ratelimit-remaining" = ( 4992 ); "x-ratelimit-reset" = ( 1669090408 ); "x-ratelimit-resource" = ( core ); "x-ratelimit-used" = ( 8 ); "x-xss-protection" = ( 0 ); } }
问题原因
- Content-Type设置错误:初始代码中
Content-Type被设置为application/x-www-form-urlencoded,但发送的是JSON格式数据,不符合API要求,需改为application/json。 - Content值类型错误:之前的代码直接将
ProjectExport对象作为content的值嵌套编码,导致最终JSON中content是一个嵌套对象,而GitHub API要求content必须是字符串类型。需先将ProjectExport编码为JSON字符串,再作为content的值传入。
解决方案代码
以下是可正常运行的代码:
struct Tester : Codable { var description : String var publc : Bool var filename : String var payload : String func files() -> Dictionary<String, Dictionary<String, String>> { return [filename : ["content" : payload]] } func output() -> Output { return Output(description: description, Public: publc, files: files()) } struct Output : Codable { var description : String var Public : Bool var files : Dictionary<String, Dictionary<String, String>> } } @objc func exportProject() { guard let projectExport = userProject?.export() else { return } guard var encodedData = encode(src: projectExport) else { print("problem encoding data") return } var gistTest = Tester(description: "please work", publc: true, filename: "justMaybe.md", payload: encodedData) sendToGist(payload: gistTest.output()) } func encode(src : ProjectExport) -> String? { let encoder = JSONEncoder() do { let data = try encoder.encode(src) return String(data: data, encoding: .utf8)! } catch {print("problem \(error)")} return nil } func encode(src : Tester.Output) -> String? { let encoder = JSONEncoder() do { let data = try encoder.encode(src) return String(data: data, encoding: .utf8)! } catch {print("problem \(error)")} return nil } func sendToGist(payload : Tester.Output) { guard var data = encode(src: payload) else { print("no data") return } let url = URL(string: "https://api.github.com/gists")! var request = URLRequest(url: url) request.httpBody = data.data(using: String.Encoding.utf8) request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue("application/vnd.github+json", forHTTPHeaderField: "Accept") request.setValue("Bearer \(StartController.token)", forHTTPHeaderField: "Authorization") request.httpMethod = "POST" let task = URLSession.shared.dataTask(with: request) { data, response, error in guard let data = data, let response = response as? HTTPURLResponse, error == nil else { // 检查基础网络错误 print("error", error ?? URLError(.badServerResponse)) return } guard (200 ... 299) ~= response.statusCode else { // 检查HTTP错误 print("statusCode should be 2xx, but is \(response.statusCode)") print("response = \(response)") return } do { print("done") } catch { print(error) // 解析错误 if let responseString = String(data: data, encoding: .utf8) { print("responseString = \(responseString)") } else { print("unable to parse response as string") } } } task.resume() }
内容的提问来源于stack exchange,提问作者Dan Donaldson
相关产品推荐
相关产品推荐

