Appearance
实现 cURL 转代码 工具
分类:developer | 标签:curl、fetch、代码生成 cURL 命令转 fetch/axios/Python/Go/Java 请求代码
2.1 功能说明
cURL 命令转 fetch/axios/Python/Go/Java 请求代码
2.1.1 使用指南
功能说明
把一条 curl 命令解析成等价的请求代码,目前支持 JavaScript(fetch)、JavaScript(axios)、Python(requests)、Go(net/http)、Java(HttpURLConnection) 五种写法。解析器为本工具自研,不依赖第三方库,命令内容不会上传到任何服务器。
支持解析的参数
请求方法:-X / --request;未显式指定时,有请求体推断为 POST,否则为 GET,-I 视为 HEAD,-G 会把数据拼到查询字符串并使用 GET。
URL:--url 指定的值,或 curl 之后第一个不是选项的参数;缺少协议时自动补 https://。
请求头:-H / --header,形如 'Key: Value',可重复多次。
请求体:-d、--data、--data-raw、--data-binary、--data-urlencode,多个数据参数会按 curl 的规则用 & 连接;工具会自动判断内容是 JSON、表单键值对还是普通文本,并据此选择目标语言里最合适的写法。
表单上传:-F / --form,支持 key=value 与 key=@filename 两种形式,生成 multipart/form-data 的对应代码。
其它:-u user:pass 转换为 Basic 认证头,-b 转为 Cookie 头,-A 转为 User-Agent 头,-e 转为 Referer 头;-s、-k、-L、--compressed 等只影响 curl 自身行为的开关会被忽略。
使用方式
大多数浏览器的开发者工具中,在网络面板右键某个请求选择「复制为 cURL」,直接粘贴到左侧输入框即可。支持带反斜杠换行的多行命令。粘贴后点击「转换」,在右侧切换语言标签查看代码,并可复制或下载为源文件。
注意事项
当命令带请求体但没有写 Content-Type 时,curl 实际会发送 application/x-www-form-urlencoded;如果工具检测到请求体是合法 JSON,会按更符合实际意图的方式补上 application/json 并给出提示,如不需要请在命令里显式指定请求头。
生成的代码以可读为优先,包含基本的错误处理和结果打印,实际使用时请根据项目规范调整(例如超时设置、连接复用、证书校验等)。-k 这类跳过证书校验的行为出于安全考虑不会翻译到代码中。
2.2 代码实现
2.2.1 组件结构
本工具是一个基于 Vue 3 <script setup> 语法的单文件组件(SFC),统一包裹在 ToolPageShell 组件内,由它提供页面标题、工具 ID、分类与「使用指南」插槽等通用外壳;核心业务逻辑(响应式状态、计算属性、事件处理函数)全部写在 <script setup> 中,输入/输出通过 el-input、el-button 等 Element Plus 组件与用户交互,所有数据均在浏览器本地处理,不会上传服务器。
2.2.2 核心逻辑概览
<script setup> 中定义的主要函数/方法:
tokenize()safeDecode()parseCurl()nextValue()take()q()indentLines()jsLiteral()pyLiteral()headersJs()headersPy()genFetch()genAxios()genPython()genGo()genJava()convert()copyCode()downloadCode()loadSample()clearAll()
2.2.3 关键实现代码
以下是该工具的完整 <script setup> 实现(核心代码)。模板(<template>)与样式(<style>)已省略。
vue
import { ref, computed } from 'vue'
import ToolPageShell from '@/components/ToolPageShell.vue'
const langs = [
{ key: 'fetch', label: 'JavaScript (fetch)', file: 'request.js' },
{ key: 'axios', label: 'JavaScript (axios)', file: 'request-axios.js' },
{ key: 'python', label: 'Python (requests)', file: 'request.py' },
{ key: 'go', label: 'Go (net/http)', file: 'main.go' },
{ key: 'java', label: 'Java (HttpURLConnection)', file: 'Main.java' }
]
const rawCurl = ref('')
const error = ref('')
const notice = ref('')
const lang = ref('fetch')
const parsed = ref(null)
const codes = ref({})
const bodyKindLabel = computed(() => {
if (!parsed.value) return ''
const map = { none: '无', json: 'JSON', form: '表单键值对', raw: '纯文本', multipart: 'multipart 表单' }
return map[parsed.value.bodyKind] || '无'
})
/* ---------- 词法分析:按 shell 规则切分 curl 命令 ---------- */
function tokenize(source) {
const s = String(source)
.replace(/\\\r?\n/g, ' ')
.replace(/\^\r?\n/g, ' ')
.replace(/\r?\n/g, ' ')
const tokens = []
let cur = ''
let started = false
let quote = null
for (let i = 0; i < s.length; i++) {
const ch = s[i]
if (quote) {
if (ch === quote) { quote = null; continue }
if (quote === '"' && ch === '\\') {
const next = s[i + 1]
if (next === '"' || next === '\\' || next === '$' || next === '`') { cur += next; i++; continue }
if (next === 'n') { cur += '\n'; i++; continue }
if (next === 't') { cur += '\t'; i++; continue }
if (next === 'r') { cur += '\r'; i++; continue }
}
cur += ch
continue
}
if (ch === '"' || ch === "'") { quote = ch; started = true; continue }
if (ch === '\\') {
const next = s[i + 1]
if (next !== undefined) { cur += next; i++; started = true }
continue
}
if (/\s/.test(ch)) {
if (started) { tokens.push(cur); cur = ''; started = false }
continue
}
cur += ch
started = true
}
if (started) tokens.push(cur)
return tokens
}
function safeDecode(s) {
try { return decodeURIComponent(s) } catch (e) { return s }
}
const NO_ARG_FLAGS = new Set([
'-s', '--silent', '-S', '--show-error', '-k', '--insecure', '-L', '--location',
'-v', '--verbose', '-i', '--include', '--compressed', '-g', '--globoff',
'-N', '--no-buffer', '-f', '--fail', '--http1.0', '--http1.1', '--http2',
'-4', '-6', '-j', '--no-keepalive', '--location-trusted', '--path-as-is'
])
const VALUE_FLAGS = new Set([
'-o', '--output', '--connect-timeout', '-m', '--max-time', '--retry',
'-x', '--proxy', '--cert', '--key', '-w', '--write-out', '--cacert',
'--max-redirs', '-T', '--upload-file', '--resolve', '--interface'
])
function parseCurl(source) {
const tokens = tokenize(source)
if (!tokens.length) throw new Error('命令为空')
let i = 0
if (tokens[i] === 'curl') i++
else if (tokens[i] && tokens[i].toLowerCase().endsWith('curl')) i++
let method = ''
let url = ''
const headers = []
const dataParts = []
const formParts = []
let user = ''
let isGet = false
let headOnly = false
const nextValue = (name) => {
i++
if (i >= tokens.length) throw new Error('选项 ' + name + ' 缺少参数值')
return tokens[i]
}
for (; i < tokens.length; i++) {
let t = tokens[i]
if (t === '') continue
// --name=value 形式
let inlineValue = null
if (t.startsWith('--') && t.indexOf('=') > 0) {
const idx = t.indexOf('=')
inlineValue = t.slice(idx + 1)
t = t.slice(0, idx)
}
const take = (name) => (inlineValue !== null ? inlineValue : nextValue(name))
if (t === '-X' || t === '--request') { method = take(t).toUpperCase(); continue }
if (/^-X./.test(t)) { method = t.slice(2).toUpperCase(); continue }
if (t === '-H' || t === '--header') { headers.push(take(t)); continue }
if (/^-H./.test(t)) { headers.push(t.slice(2)); continue }
if (t === '--url') { url = take(t); continue }
if (t === '-d' || t === '--data' || t === '--data-raw' || t === '--data-ascii' || t === '--data-binary') {
dataParts.push(take(t)); continue
}
if (t === '--data-urlencode') {
const v = take(t)
const eq = v.indexOf('=')
if (eq > 0) dataParts.push(v.slice(0, eq) + '=' + encodeURIComponent(v.slice(eq + 1)))
else dataParts.push(encodeURIComponent(v))
continue
}
if (/^-d./.test(t)) { dataParts.push(t.slice(2)); continue }
if (t === '-F' || t === '--form' || t === '--form-string') { formParts.push(take(t)); continue }
if (t === '-u' || t === '--user') { user = take(t); continue }
if (t === '-b' || t === '--cookie') { headers.push('Cookie: ' + take(t)); continue }
if (t === '-A' || t === '--user-agent') { headers.push('User-Agent: ' + take(t)); continue }
if (t === '-e' || t === '--referer') { headers.push('Referer: ' + take(t)); continue }
if (t === '-G' || t === '--get') { isGet = true; continue }
if (t === '-I' || t === '--head') { headOnly = true; continue }
if (NO_ARG_FLAGS.has(t)) continue
if (VALUE_FLAGS.has(t)) { if (inlineValue === null) nextValue(t); continue }
// 合并式短选项,如 -fsSL
if (/^-[a-zA-Z]{2,}$/.test(t)) {
const chars = t.slice(1)
let allKnown = true
for (const c of chars) if (!NO_ARG_FLAGS.has('-' + c)) allKnown = false
if (allKnown) continue
}
if (t.startsWith('-')) continue
if (!url) url = t
}
if (!url) throw new Error('未识别到请求 URL')
if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(url)) url = 'https://' + url
let body = dataParts.join('&')
if (isGet && body) {
url += (url.indexOf('?') === -1 ? '?' : '&') + body
body = ''
}
const headerList = []
for (const h of headers) {
const idx = h.indexOf(':')
if (idx === -1) continue
const key = h.slice(0, idx).trim()
const value = h.slice(idx + 1).trim()
if (key) headerList.push({ key, value })
}
if (user) {
let encoded = ''
try { encoded = btoa(unescape(encodeURIComponent(user))) } catch (e) { encoded = '' }
if (encoded) headerList.push({ key: 'Authorization', value: 'Basic ' + encoded })
}
const forms = formParts.map(f => {
const idx = f.indexOf('=')
if (idx === -1) return { key: f, value: '', isFile: false }
const key = f.slice(0, idx)
const val = f.slice(idx + 1)
if (val.startsWith('@') || val.startsWith('<')) return { key, value: val.slice(1), isFile: true }
return { key, value: val, isFile: false }
})
// 判定请求体类型
let bodyKind = 'none'
let jsonValue = null
let formPairs = null
if (forms.length) {
bodyKind = 'multipart'
} else if (body) {
const trimmed = body.trim()
const looksJson = (trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']'))
if (looksJson) {
try {
jsonValue = JSON.parse(trimmed)
bodyKind = 'json'
} catch (e) {
bodyKind = 'raw'
}
} else if (/^[^\s=&]+=[^&]*(&[^\s=&]+=[^&]*)*$/.test(trimmed)) {
bodyKind = 'form'
formPairs = trimmed.split('&').map(pair => {
const eq = pair.indexOf('=')
return {
key: safeDecode(pair.slice(0, eq)),
value: safeDecode(pair.slice(eq + 1).replace(/\+/g, ' '))
}
})
} else {
bodyKind = 'raw'
}
}
if (!method) {
if (headOnly) method = 'HEAD'
else if (bodyKind !== 'none') method = 'POST'
else method = 'GET'
}
const hasContentType = headerList.some(h => h.key.toLowerCase() === 'content-type')
let inferredContentType = ''
if (!hasContentType) {
if (bodyKind === 'json') inferredContentType = 'application/json'
else if (bodyKind === 'form') inferredContentType = 'application/x-www-form-urlencoded'
else if (bodyKind === 'raw') inferredContentType = 'text/plain'
}
if (inferredContentType) headerList.push({ key: 'Content-Type', value: inferredContentType })
return { method, url, headers: headerList, body, bodyKind, jsonValue, formPairs, forms, inferredContentType }
}
/* ---------- 各语言字面量工具 ---------- */
function q(s) { return JSON.stringify(String(s)) }
function indentLines(text, spaces) {
const pad = ' '.repeat(spaces)
return String(text).split('\n').map((l, i) => (i === 0 ? l : pad + l)).join('\n')
}
function jsLiteral(value, level) {
const ind = ' '.repeat(level)
const indIn = ' '.repeat(level + 1)
if (value === null) return 'null'
if (typeof value === 'boolean' || typeof value === 'number') return String(value)
if (typeof value === 'string') return q(value)
if (Array.isArray(value)) {
if (!value.length) return '[]'
return '[\n' + value.map(v => indIn + jsLiteral(v, level + 1)).join(',\n') + '\n' + ind + ']'
}
const keys = Object.keys(value)
if (!keys.length) return '{}'
return '{\n' + keys.map(k => indIn + q(k) + ': ' + jsLiteral(value[k], level + 1)).join(',\n') + '\n' + ind + '}'
}
function pyLiteral(value, level) {
const ind = ' '.repeat(level)
const indIn = ' '.repeat(level + 1)
if (value === null) return 'None'
if (typeof value === 'boolean') return value ? 'True' : 'False'
if (typeof value === 'number') return String(value)
if (typeof value === 'string') return q(value)
if (Array.isArray(value)) {
if (!value.length) return '[]'
return '[\n' + value.map(v => indIn + pyLiteral(v, level + 1)).join(',\n') + '\n' + ind + ']'
}
const keys = Object.keys(value)
if (!keys.length) return '{}'
return '{\n' + keys.map(k => indIn + q(k) + ': ' + pyLiteral(value[k], level + 1)).join(',\n') + '\n' + ind + '}'
}
function headersJs(list, level) {
if (!list.length) return '{}'
const ind = ' '.repeat(level)
const indIn = ' '.repeat(level + 1)
return '{\n' + list.map(h => indIn + q(h.key) + ': ' + q(h.value)).join(',\n') + '\n' + ind + '}'
}
function headersPy(list) {
if (!list.length) return '{}'
return '{\n' + list.map(h => ' ' + q(h.key) + ': ' + q(h.value)).join(',\n') + '\n}'
}
/* ---------- 代码生成 ---------- */
function genFetch(p) {
const lines = []
lines.push('const url = ' + q(p.url) + ';')
lines.push('')
if (p.bodyKind === 'json') {
lines.push('const payload = ' + jsLiteral(p.jsonValue, 0) + ';')
lines.push('')
} else if (p.bodyKind === 'form') {
lines.push('const payload = new URLSearchParams();')
p.formPairs.forEach(pair => lines.push('payload.append(' + q(pair.key) + ', ' + q(pair.value) + ');'))
lines.push('')
} else if (p.bodyKind === 'multipart') {
lines.push('const payload = new FormData();')
p.forms.forEach(f => {
if (f.isFile) lines.push('// ' + f.key + ' 对应文件 ' + f.value + ',浏览器中请传入 File 对象')
lines.push('payload.append(' + q(f.key) + ', ' + (f.isFile ? '/* File 对象 */ fileInput.files[0]' : q(f.value)) + ');')
})
lines.push('')
} else if (p.bodyKind === 'raw') {
lines.push('const payload = ' + q(p.body) + ';')
lines.push('')
}
const headerList = p.bodyKind === 'multipart'
? p.headers.filter(h => h.key.toLowerCase() !== 'content-type')
: p.headers
lines.push('const options = {')
lines.push(' method: ' + q(p.method) + ',')
lines.push(' headers: ' + indentLines(headersJs(headerList, 1), 2) + (p.bodyKind === 'none' ? '' : ','))
if (p.bodyKind === 'json') lines.push(' body: JSON.stringify(payload)')
else if (p.bodyKind === 'form') lines.push(' body: payload.toString()')
else if (p.bodyKind === 'multipart' || p.bodyKind === 'raw') lines.push(' body: payload')
lines.push('};')
lines.push('')
lines.push('fetch(url, options)')
lines.push(' .then((response) => {')
lines.push(' if (!response.ok) throw new Error("HTTP " + response.status);')
lines.push(' return response.json();')
lines.push(' })')
lines.push(' .then((data) => console.log(data))')
lines.push(' .catch((err) => console.error(err));')
return lines.join('\n')
}
function genAxios(p) {
const lines = []
lines.push('import axios from "axios";')
lines.push('')
if (p.bodyKind === 'json') {
lines.push('const data = ' + jsLiteral(p.jsonValue, 0) + ';')
lines.push('')
} else if (p.bodyKind === 'form') {
lines.push('const data = new URLSearchParams();')
p.formPairs.forEach(pair => lines.push('data.append(' + q(pair.key) + ', ' + q(pair.value) + ');'))
lines.push('')
} else if (p.bodyKind === 'multipart') {
lines.push('const data = new FormData();')
p.forms.forEach(f => {
if (f.isFile) lines.push('// ' + f.key + ' 对应文件 ' + f.value + ',Node 环境可用 fs.createReadStream 传入')
lines.push('data.append(' + q(f.key) + ', ' + (f.isFile ? '/* File 对象 */ fileInput.files[0]' : q(f.value)) + ');')
})
lines.push('')
} else if (p.bodyKind === 'raw') {
lines.push('const data = ' + q(p.body) + ';')
lines.push('')
}
const headerList = p.bodyKind === 'multipart'
? p.headers.filter(h => h.key.toLowerCase() !== 'content-type')
: p.headers
lines.push('const config = {')
lines.push(' method: ' + q(p.method.toLowerCase()) + ',')
lines.push(' url: ' + q(p.url) + ',')
lines.push(' headers: ' + indentLines(headersJs(headerList, 1), 2) + (p.bodyKind === 'none' ? '' : ','))
if (p.bodyKind !== 'none') lines.push(' data: data')
lines.push('};')
lines.push('')
lines.push('axios(config)')
lines.push(' .then((response) => console.log(response.data))')
lines.push(' .catch((error) => console.error(error.message));')
return lines.join('\n')
}
function genPython(p) {
const lines = []
lines.push('import requests')
lines.push('')
lines.push('url = ' + q(p.url))
lines.push('headers = ' + headersPy(p.bodyKind === 'multipart' ? p.headers.filter(h => h.key.toLowerCase() !== 'content-type') : p.headers))
const kwargs = ['url', 'headers=headers']
if (p.bodyKind === 'json') {
lines.push('payload = ' + pyLiteral(p.jsonValue, 0))
kwargs.push('json=payload')
} else if (p.bodyKind === 'form') {
const obj = {}
p.formPairs.forEach(pair => { obj[pair.key] = pair.value })
lines.push('payload = ' + pyLiteral(obj, 0))
kwargs.push('data=payload')
} else if (p.bodyKind === 'multipart') {
const fields = []
p.forms.forEach(f => {
if (f.isFile) fields.push(' ' + q(f.key) + ': open(' + q(f.value) + ', "rb")')
else fields.push(' ' + q(f.key) + ': (None, ' + q(f.value) + ')')
})
lines.push('files = {\n' + fields.join(',\n') + '\n}')
kwargs.push('files=files')
} else if (p.bodyKind === 'raw') {
lines.push('payload = ' + q(p.body))
kwargs.push('data=payload.encode("utf-8")')
}
lines.push('')
const fn = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'].indexOf(p.method) >= 0
? 'requests.' + p.method.toLowerCase()
: 'requests.request'
if (fn === 'requests.request') {
lines.push('response = requests.request(' + q(p.method) + ', ' + kwargs.join(', ') + ', timeout=30)')
} else {
lines.push('response = ' + fn + '(' + kwargs.join(', ') + ', timeout=30)')
}
lines.push('')
lines.push('print(response.status_code)')
lines.push('print(response.text)')
return lines.join('\n')
}
function genGo(p) {
const tick = String.fromCharCode(96)
const imports = ['\t"fmt"', '\t"io"', '\t"net/http"']
const needsReader = p.bodyKind === 'json' || p.bodyKind === 'form' || p.bodyKind === 'raw'
const isMultipart = p.bodyKind === 'multipart'
if (needsReader) imports.push('\t"strings"')
if (isMultipart) { imports.push('\t"bytes"'); imports.push('\t"mime/multipart"'); imports.push('\t"os"') }
imports.sort()
const lines = []
lines.push('package main')
lines.push('')
lines.push('import (')
imports.forEach(im => lines.push(im))
lines.push(')')
lines.push('')
lines.push('func main() {')
lines.push('\turl := ' + q(p.url))
if (needsReader) {
const safe = p.body.indexOf(tick) === -1
if (safe) lines.push('\tpayload := strings.NewReader(' + tick + p.body + tick + ')')
else lines.push('\tpayload := strings.NewReader(' + q(p.body) + ')')
} else if (isMultipart) {
lines.push('')
lines.push('\tbody := &bytes.Buffer{}')
lines.push('\twriter := multipart.NewWriter(body)')
p.forms.forEach(f => {
if (f.isFile) {
lines.push('\tfile, err := os.Open(' + q(f.value) + ')')
lines.push('\tif err != nil {')
lines.push('\t\tfmt.Println(err)')
lines.push('\t\treturn')
lines.push('\t}')
lines.push('\tdefer file.Close()')
lines.push('\tpart, err := writer.CreateFormFile(' + q(f.key) + ', ' + q(f.value) + ')')
lines.push('\tif err != nil {')
lines.push('\t\tfmt.Println(err)')
lines.push('\t\treturn')
lines.push('\t}')
lines.push('\tio.Copy(part, file)')
} else {
lines.push('\twriter.WriteField(' + q(f.key) + ', ' + q(f.value) + ')')
}
})
lines.push('\twriter.Close()')
}
lines.push('')
const bodyArg = needsReader ? 'payload' : (isMultipart ? 'body' : 'nil')
lines.push('\treq, err := http.NewRequest(' + q(p.method) + ', url, ' + bodyArg + ')')
lines.push('\tif err != nil {')
lines.push('\t\tfmt.Println(err)')
lines.push('\t\treturn')
lines.push('\t}')
const headerList = isMultipart ? p.headers.filter(h => h.key.toLowerCase() !== 'content-type') : p.headers
headerList.forEach(h => lines.push('\treq.Header.Set(' + q(h.key) + ', ' + q(h.value) + ')'))
if (isMultipart) lines.push('\treq.Header.Set("Content-Type", writer.FormDataContentType())')
lines.push('')
lines.push('\tres, err := http.DefaultClient.Do(req)')
lines.push('\tif err != nil {')
lines.push('\t\tfmt.Println(err)')
lines.push('\t\treturn')
lines.push('\t}')
lines.push('\tdefer res.Body.Close()')
lines.push('')
lines.push('\tdata, err := io.ReadAll(res.Body)')
lines.push('\tif err != nil {')
lines.push('\t\tfmt.Println(err)')
lines.push('\t\treturn')
lines.push('\t}')
lines.push('\tfmt.Println(res.Status)')
lines.push('\tfmt.Println(string(data))')
lines.push('}')
return lines.join('\n')
}
function genJava(p) {
const hasBody = p.bodyKind !== 'none'
const isMultipart = p.bodyKind === 'multipart'
const lines = []
lines.push('import java.io.BufferedReader;')
lines.push('import java.io.InputStreamReader;')
lines.push('import java.io.OutputStream;')
lines.push('import java.net.HttpURLConnection;')
lines.push('import java.net.URL;')
lines.push('import java.nio.charset.StandardCharsets;')
if (isMultipart) lines.push('import java.nio.file.Files;')
if (isMultipart) lines.push('import java.nio.file.Paths;')
lines.push('')
lines.push('public class Main {')
lines.push(' public static void main(String[] args) throws Exception {')
lines.push(' URL url = new URL(' + q(p.url) + ');')
lines.push(' HttpURLConnection conn = (HttpURLConnection) url.openConnection();')
lines.push(' conn.setRequestMethod(' + q(p.method) + ');')
lines.push(' conn.setConnectTimeout(15000);')
lines.push(' conn.setReadTimeout(30000);')
const headerList = isMultipart ? p.headers.filter(h => h.key.toLowerCase() !== 'content-type') : p.headers
headerList.forEach(h => lines.push(' conn.setRequestProperty(' + q(h.key) + ', ' + q(h.value) + ');'))
if (isMultipart) {
lines.push('')
lines.push(' String boundary = "----JavaFormBoundary" + System.currentTimeMillis();')
lines.push(' conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);')
lines.push(' conn.setDoOutput(true);')
lines.push(' try (OutputStream os = conn.getOutputStream()) {')
p.forms.forEach(f => {
if (f.isFile) {
lines.push(' os.write(("--" + boundary + "\\r\\n").getBytes(StandardCharsets.UTF_8));')
lines.push(' os.write(("Content-Disposition: form-data; name=' + f.key + '; filename=' + f.value + '\\r\\n").getBytes(StandardCharsets.UTF_8));')
lines.push(' os.write("Content-Type: application/octet-stream\\r\\n\\r\\n".getBytes(StandardCharsets.UTF_8));')
lines.push(' os.write(Files.readAllBytes(Paths.get(' + q(f.value) + ')));')
lines.push(' os.write("\\r\\n".getBytes(StandardCharsets.UTF_8));')
} else {
lines.push(' os.write(("--" + boundary + "\\r\\n").getBytes(StandardCharsets.UTF_8));')
lines.push(' os.write(("Content-Disposition: form-data; name=' + f.key + '\\r\\n\\r\\n").getBytes(StandardCharsets.UTF_8));')
lines.push(' os.write(' + q(f.value) + '.getBytes(StandardCharsets.UTF_8));')
lines.push(' os.write("\\r\\n".getBytes(StandardCharsets.UTF_8));')
}
})
lines.push(' os.write(("--" + boundary + "--\\r\\n").getBytes(StandardCharsets.UTF_8));')
lines.push(' }')
} else if (hasBody) {
lines.push('')
lines.push(' String body = ' + q(p.body) + ';')
lines.push(' conn.setDoOutput(true);')
lines.push(' try (OutputStream os = conn.getOutputStream()) {')
lines.push(' os.write(body.getBytes(StandardCharsets.UTF_8));')
lines.push(' }')
}
lines.push('')
lines.push(' int status = conn.getResponseCode();')
lines.push(' BufferedReader reader = new BufferedReader(new InputStreamReader(')
lines.push(' status >= 400 ? conn.getErrorStream() : conn.getInputStream(), StandardCharsets.UTF_8));')
lines.push(' StringBuilder sb = new StringBuilder();')
lines.push(' String line;')
lines.push(' while ((line = reader.readLine()) != null) {')
lines.push(' sb.append(line);')
lines.push(' }')
lines.push(' reader.close();')
lines.push(' conn.disconnect();')
lines.push('')
lines.push(' System.out.println(status);')
lines.push(' System.out.println(sb.toString());')
lines.push(' }')
lines.push('}')
return lines.join('\n')
}
function convert() {
error.value = ''
notice.value = ''
parsed.value = null
codes.value = {}
if (!rawCurl.value.trim()) { error.value = '请输入 curl 命令'; return }
let p
try {
p = parseCurl(rawCurl.value)
} catch (e) {
error.value = '解析失败:' + e.message
return
}
parsed.value = p
codes.value = {
fetch: genFetch(p),
axios: genAxios(p),
python: genPython(p),
go: genGo(p),
java: genJava(p)
}
if (p.inferredContentType === 'application/json') {
notice.value = '原命令未指定 Content-Type,检测到请求体是 JSON,已按 application/json 生成(curl 默认会发送 application/x-www-form-urlencoded)'
}
}
function copyCode() {
const code = codes.value[lang.value]
if (code && navigator.clipboard) navigator.clipboard.writeText(code)
}
function downloadCode() {
const code = codes.value[lang.value]
if (!code) return
const meta = langs.find(l => l.key === lang.value)
const blob = new Blob([code], { type: 'text/plain;charset=utf-8' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = meta ? meta.file : 'request.txt'
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
URL.revokeObjectURL(url)
}
function loadSample() {
rawCurl.value = [
"curl -X POST 'https://api.example.com/v1/users' \\",
" -H 'Content-Type: application/json' \\",
" -H 'Authorization: Bearer token123' \\",
' -d \'{"name":"张三","age":28,"tags":["vip","new"],"active":true}\''
].join('\n')
convert()
}
function clearAll() {
rawCurl.value = ''
parsed.value = null
codes.value = {}
error.value = ''
notice.value = ''
}2.3 效果截图
