Upload - Go
使用 Go 编程语言编写的请求示例。
package fire
import (
"bytes"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"golang/rsa"
"io"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
// 服务商(agency)的编号
// 此处使用 agency_no = 100000000000000000 作为模拟
// 真实请求场景,请咨询 ice-run 的技术支持获取正 式的 服务商编号 agency_no
const AgencyNo = "100000000000000000"
// 安全算法 RSA
const Security = "RSA"
// ice-run 的 公钥
const PublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC78QqLKTkVcwvB2Scm4IoImOXmIthVmx4HHr5Im09aol2r2UlMK3jl4lamdxktvjFswWx41hQBD4tyYFHTY+mQJsRK8Zwm7LZmLJDHcgibIuLxEddln+MI+rWwT+SOdOt7lBvd9PcO4nYr/3Nqkyk8L4PrT+GF7dpVk8iE/VpTwQIDAQAB"
// agency 的 私钥
const PrivateKey = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAOyC76Y+/2NqkkIVUtAHkbZkBYI2XXkuqE9o4oIDkbyAY04jC5MLa3oVc3uGG7T/Elodo9dIbIxNh7/ir7GQLR/hY+ookHAewurJ1z4Jn5TQeRk3+nYQcX6a6orfUOMffwcn0Jtat8D+cNAu3uCi+N7Ez/yNpItNORYqTa8AkgzrAgMBAAECgYAS/rsfl3ysb+E6RHsnsQvvYZ4dpJ8iPfCPnCVg+sdoI8mV+3ORBkBGCFYDjDRKd5fyO+IuRqdNJ2bpLtwcfy9YchcV/x40GIuAlre6A6qt4Raq+0l4FJufaqgdmSiLxd5zPX79gdMvBqTbdYzCSDGP5Pf3pTqIS3iKvSixFTQBzQJBAO6k6rE6JE/EHrbRjVtZNitDZ8rouCL0s9nGsgIDBSfgOCClO+HlZKcHq6W9ry8j1gOFeGH2rF1yMslA/ZymRL0CQQD9tk/gk9Xs1Eg1if7SLwvNxUP+z96oEqQTF26tSudPRwOO88fnfE4ZZ4heBt1QI+s7IhG39qecsFW269nvKfbHAkEArXmEgUBalQFjslGyB+1Zyyk8keuJrx9ifbRKQdwgK1R6eICkfxlZiXGx/NFeP041jGnBkLTXpzYUZOexc+YJoQJBANR3r87vnxAU+l+zr62O7oCk+XtT0y/HZJYEYpBHEQyn+MfnSXqG89R8iovLjd0GJ4E+173KlrU2SqHEQ57w8pMCQAEoDjFUXpYzBl8e0M9XsSpGY531mbOl8ASnVp4OSyavaCsn56eEkL9TI5q69KrYUEA4PN3BazMWfll2s/wu6G0="
// ice-run 的 test 环境的 API 网关 的域名地址
// 正式生产环境的域名地址请查阅 ice-run 的技术文档
const Domain = "https://test-fire-api.ice.run"
// 示例地址,仅用于测试加密解密加签验签流程,无任何实际业务场景
const Upload = Domain + "/file-service/api/upload"
// 请求 body 结构
type Request struct {
Param string `json:"param"`
}
// 响应 body 结构
type Response struct {
Code string `json:"code"`
Message string `json:"message"`
Data string `json:"data"`
}
// 模拟请求 upload 接口
func TestFireFile(t *testing.T) {
url := Upload
fmt.Println("request url :", url)
// 对方公钥
publicKey, err := rsa.PublicKey(PublicKey)
if err != nil {
t.Fatal(err)
}
// 己方私钥
privateKey, err := rsa.PrivateKey(PrivateKey)
if err != nil {
t.Fatal(err)
}
// 待上传的文件
filename := "/tmp/0.txt"
file, err := os.Open(filename)
if err != nil {
panic(err)
}
defer func(file *os.File) {
err := file.Close()
if err != nil {
t.Fatal(err)
}
}(file)
// 计算文件 hash
hash := sha256.New()
_, err = io.Copy(hash, file)
if err != nil {
t.Fatal(err)
}
// hash 明文
hashPlains := hex.EncodeToString(hash.Sum(nil))
fmt.Println("hash plains :", hashPlains)
// hash 密文:agency 使用 ice-run 公钥 对 hash 明文加密后得到的密文
hashCipher, err := rsa.Encrypt(publicKey, hashPlains)
if err != nil {
t.Fatal(err)
}
fmt.Println("hash cipher :", hashCipher)
// 请求签名:agency 使用 私钥 对 hash 密文进行签名
// 当 ice-run 收到请求时,将会使用 agency 公钥 进行验签
sign, err := rsa.Sign(privateKey, hashCipher)
if err != nil {
t.Fatal(err)
}
fmt.Println("request sign : ", sign)
// 请求 body
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
err = writer.WriteField("hash", hashPlains)
if err != nil {
t.Fatal(err)
}
file, _ = os.Open(filename)
defer func(file *os.File) {
err := file.Close()
if err != nil {
t.Fatal(err)
}
}(file)
part, err := writer.CreateFormFile("file", filepath.Base(filename))
if err != nil {
t.Fatal(err)
}
if _, err := io.Copy(part, file); err != nil {
t.Fatal(err)
}
if err := writer.Close(); err != nil {
t.Fatal(err)
}
// 请求时间,当前世界标准时间的字符串,格式为 RFC-1123 标准,服务器会校验时间差,不允许超过设置的时间间隔(默认为 2 分钟)
xTime := strings.Replace(time.Now().UTC().Format(time.RFC1123), "UTC", "GMT", -1)
// 链路追踪号(防止重放攻击的请求编号):单位时间内(默认为 1 分钟)不重复的字符串,建议使用 uuid 或者时间戳+随机数。限制长度 = 32。正则表达式 = ^[0-9a-f]{32}$
randomBytes := make([]byte, 16)
_, err = io.ReadFull(rand.Reader, make([]byte, 16))
if err != nil {
t.Fatal(err)
}
xTrace := hex.EncodeToString(randomBytes)
// http 客户端
// 此处使用 golang 标准库模拟,实际场景建议自行选择 HTTP 客户端组件包
// 发起 HTTP 请求之前,请确认 ${DOMAIN} 可以正常访问,部分客户端的服务器环境可能需要设置请求代理
client := &http.Client{}
request, err := http.NewRequest("POST", url, body)
if err != nil {
t.Fatal(err)
}
// 请求头
request.Header.Add("Content-Type", writer.FormDataContentType())
request.Header.Add("X-Client", AgencyNo)
request.Header.Add("X-Security", Security)
request.Header.Add("X-Hash", hashCipher)
request.Header.Add("X-Sign", sign)
request.Header.Add("X-Time", xTime)
request.Header.Add("X-Trace", xTrace)
fmt.Println("request header : ", request.Header)
// 发送请求
response, err := client.Do(request)
if err != nil {
t.Fatal(err)
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
t.Fatal(err)
}
}(response.Body)
// 响应状态码
statusCode := response.StatusCode
fmt.Println("response status :", statusCode)
// 响应 header
fmt.Println("response header :", response.Header)
sign = response.Header.Get("X-Sign")
fmt.Println("response sign :", sign)
// 响应 body ,json 格式的字符串
bodyBytes, err := io.ReadAll(response.Body)
if err != nil {
t.Fatal(err)
}
responseBody := string(bodyBytes)
fmt.Println("response body :", responseBody)
// 响应对象
// json 结构,此处使用自定义结构体模拟
// 数据的 键 有 code 和 message 和 data
// 其中 data 为 json 序列化后的字符串 RSA 加密后的密文
var responseObject Response
err = json.Unmarshal(bodyBytes, &responseObject)
if err != nil {
t.Fatal(err)
}
// 响应码,默认 000000 表示请求成功
code := responseObject.Code
fmt.Println("response code :", code)
// 响应 数据密文
dataCipher := responseObject.Data
fmt.Println("data cipher :", dataCipher)
// 响应验签:ice-run 处理响应时,会使用 ice-run 私钥对响应密文进行签名
// agency 收到响应后,需要使用 ice-run 公钥进行验签
verify, err := rsa.Verify(publicKey, dataCipher, sign)
if err != nil || !verify {
t.Fatal(errors.New("verification failed"))
}
// 响应 数据明文
dataPlains, err := rsa.Decrypt(privateKey, dataCipher)
if err != nil {
t.Fatal(err)
}
fmt.Println("data plains :", dataPlains)
var data map[string]any
err = json.Unmarshal([]byte(dataPlains), &data)
if err != nil {
t.Fatal(err)
}
}