しおメモ

雑多な技術系ブログです。ニッチな内容が多いです。

URLRequestをcurlコマンドに変換する

サーバーサイドエンジニアとやり取りする時など、何かとcurlコマンドが欲しい機会があると思います。
Charlesなどの外部ツールを使っても出来ますが、LLDBで扱えると時短になります。

Swift側のextension

HEADリクエストなどを考慮していないため、厳密ではない部分はありますが、実務で使うようなリクエストにはシンプルなコードで対応できます。
必要に応じて、オプションをcomponentsに追加することで調整可能です。

extension URLRequest {
    var curlCommand: String? {
        guard let method = httpMethod,
              let url = url?.absoluteString else {
            return nil
        }

        var components = ["curl", "-X", method, "'\(url)'"]
        for (key, value) in allHTTPHeaderFields ?? [:] {
            components.append("-H")
            components.append("'\(key): \(value)'")
        }
        if let httpBody, let body = String(data: httpBody, encoding: .utf8) {
            components.append("-d")
            components.append("'\(body)'")
        }

        return components.joined(separator: " ")
    }
}
let request = URLRequest(url: .init(string: "https://www.hogehoge.com")!)
print(request.curlCommand!) // curl -X GET 'https://www.hogehoge.com'

LLDB用のPythonスクリプト

プロダクトに入れる必要は全くないので、swiftファイルをプロジェクト外に置いておき、LLDBのスクリプトで読ませるのがおすすめです。
今回はSwift側で整形するので、Python側の処理はswiftファイルを読み込んで、そのままLLDBに食わせるだけです。

import lldb
import lldbcommon as common
import os


@lldb.command("curl")
def print_curl(debugger, exp, result, dict):
    path = os.path.join(os.path.dirname(__file__), 'swift/URLRequest+curlCommand.swift')
    with open(path, 'r') as f:
        common.evaluate(f.read())

    common.evaluate(f'print({exp}.curlCommand!)')

毎回swiftファイルをロードしてしまうので、 気になる方はPython側で制御を追加してください。

common.evaluateの実装の一例は以下の通りです。

def evaluate(exp):
    options = lldb.SBExpressionOptions()
    options.SetLanguage(lldb.eLanguageTypeSwift)
    options.SetTimeoutInMicroSeconds(3 * 1000 * 1000)
    options.SetTrapExceptions(False)

    frame = (
        lldb.debugger.GetSelectedTarget()
        .GetProcess()
        .GetSelectedThread()
        .GetSelectedFrame()
    )

    value = frame.EvaluateExpression(exp, options)
    error = value.GetError()
    if not error.Success() and error.value != 0x1001:
        print(error)
        
    return value