Commit 7eeecb16 authored by luxq's avatar luxq

add code

parent 9d11e580
Pipeline #805 canceled with stages
This diff is collapsed.
# caddyproxy
caddyproxy
package caddy
import (
"bytes"
"caddyproxy/types"
"caddyproxy/utils"
"fmt"
log "github.com/sirupsen/logrus"
"io/ioutil"
"net/http"
"path/filepath"
"text/template"
)
type CaddyAPI struct {
Url string // URL of the Caddy API
RootDir string // Root directory of the Caddy to store website files and .caddy files.
client *http.Client
}
func NewCaddyAPI(url string, root string) *CaddyAPI {
return &CaddyAPI{Url: url, RootDir: root, client: &http.Client{}}
}
func (c *CaddyAPI) buildPayloadForCreateWebsite(domain string, root string) (string, error) {
// build the payload for creating a new website.
buffer := bytes.NewBufferString("")
cfg := StaticWebsiteCreatePayload{
DomainName: domain,
RootDir: root,
WebsiteCaddyFile: filepath.Join(c.RootDir, fmt.Sprintf("%s.caddy", domain)),
}
tmpl, err := template.New("test").Parse(staticWebsiteCaddyFileTempl)
if err != nil {
log.WithError(err).Error("failed to parse website caddyfile template")
return "", err
}
err = tmpl.Execute(buffer, cfg)
if err != nil {
log.WithError(err).Error("failed to build website caddyfile")
return "", err
}
return buffer.String(), nil
}
func (c *CaddyAPI) newCaddyFile(domain string, root string) error {
buffer := bytes.NewBufferString("")
caddypath := filepath.Join(c.RootDir, fmt.Sprintf("%s.caddy", domain))
// build CaddyFileTemplate.
cfg := NewWebsiteCaddyFile{
DomainName: domain,
RootDir: root,
}
tmpl, err := template.New("test").Parse(staticWebsiteCaddyFileTempl)
if err != nil {
log.WithError(err).Error("failed to parse website caddyfile template")
return err
}
err = tmpl.Execute(buffer, cfg)
if err != nil {
log.WithError(err).Error("failed to build website caddyfile")
return err
}
if err = utils.CreateFile(caddypath, buffer.String()); err != nil {
log.WithError(err).Error("failed to create website caddyfile")
return err
}
return nil
}
func (c *CaddyAPI) CreateWebsite(domain string, resource string) error {
// unzip resource to the website root directory.
websiteRoot := filepath.Join(c.RootDir, "websites", domain)
if err := utils.Unzip(resource, websiteRoot); err != nil {
return err
}
// create caddyfile.
if err := c.newCaddyFile(domain, websiteRoot); err != nil {
return err
}
// build api payload.
payload, err := c.buildPayloadForCreateWebsite(domain, websiteRoot)
if err != nil {
return err
}
// send put request to the caddy api.
path := fmt.Sprintf("%s/config/apps/http/servers/srv0/routes/%d", c.Url, 0)
if err := c.put(path, []byte(payload)); err != nil {
return err
}
return nil
}
func (c *CaddyAPI) ForwardWebsite(param types.ForwardWebsite) error {
// todo: create a new site in Caddy and set the target to the domain.
return nil
}
func (c *CaddyAPI) put(path string, data []byte) error {
// send a get request to the Caddy API.
req, err := http.NewRequest("PUT", path, bytes.NewBuffer(data))
if err != nil {
log.WithError(err).Error("failed to create request")
return err
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.client.Do(req)
if err != nil {
log.WithError(err).Error("failed to send request")
return err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.WithError(err).Error("failed to read response body")
return err
}
if resp.StatusCode != http.StatusOK {
log.WithFields(log.Fields{
"status": resp.StatusCode,
"message": string(body),
}).Error("failed to put data")
return fmt.Errorf("failed to put data")
}
return nil
}
package caddy
import "testing"
func TestCaddyAPI_CreateWebsite(t *testing.T) {
caddyAPI := NewCaddyAPI("http://3.114.44.103:32019", "/home/ubuntu/caddy")
err := caddyAPI.CreateWebsite("mp4.bitheart.io", "")
if err != nil {
t.Errorf("CreateWebsite() error = %v", err)
} else {
t.Log("CreateWebsite() success")
}
}
package caddy
var staticWebsiteCaddyFileTempl = `
{{ .DomainName }} {
root * {{ .RootDir }}
file_server
import COMMON_CONFIG
}
`
var newWebsitePayloadTempl = `
{
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"handler": "vars",
"root": "{{ .RootDir }}"
}
]
},
{
"handle": [
{
"handler": "headers",
"response": {
"set": {
"Strict-Transport-Security": [
"max-age=63072000"
]
}
}
}
],
"match": [
{
"path": [
"/"
]
}
]
},
{
"handle": [
{
"encodings": {
"gzip": {},
"zstd": {}
},
"handler": "encode",
"prefer": [
"zstd",
"gzip"
]
},
{
"handler": "file_server",
"hide": [
"./Caddyfile",
"{{ .WebsiteCaddyFile }}"
]
}
]
}
]
}
],
"match": [
{
"host": [
"{{ .DomainName }}"
]
}
],
"terminal": true
}`
package caddy
type NewWebsiteCaddyFile struct {
DomainName string
RootDir string
}
type StaticWebsiteCreatePayload struct {
DomainName string
WebsiteCaddyFile string
RootDir string
}
package root
import (
"caddyproxy/command/run"
"fmt"
"github.com/spf13/cobra"
"os"
)
type RootCommand struct {
baseCmd *cobra.Command
}
func NewRootCommand() *RootCommand {
rootCommand := &RootCommand{
baseCmd: &cobra.Command{
Short: "CaddyProxy is a tool to control caddy config via API",
},
}
rootCommand.baseCmd.CompletionOptions.HiddenDefaultCmd = true
rootCommand.registerSubCommands()
return rootCommand
}
func (rc *RootCommand) registerSubCommands() {
rc.baseCmd.AddCommand(run.GetCommand())
}
func (rc *RootCommand) Execute() {
if err := rc.baseCmd.Execute(); err != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
package run
const (
hostFlag = "host"
portFlag = "port"
caddyUrlFlag = "caddy-url"
caddyRootFlag = "caddy-root"
downloadDirFlag = "download-dir"
logFlag = "log"
)
type serviceParam struct {
host string
port int
caddyUrl string
caddyRoot string
logPath string
downloadDir string
}
var (
params = &serviceParam{
host: "0.0.0.0",
port: 9000,
caddyUrl: "",
caddyRoot: "",
logPath: "/root/data/service.log",
downloadDir: "/root/data/download",
}
)
package run
import (
"caddyproxy/openapi"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"os"
)
func GetCommand() *cobra.Command {
runtimeCmd := &cobra.Command{
Use: "run",
Short: "Run the server to provide api.",
Run: runCommand,
}
log.SetFormatter(&log.TextFormatter{
DisableColors: true,
FullTimestamp: true,
})
setFlags(runtimeCmd)
return runtimeCmd
}
func setlog(path string) func() {
if path == "" {
return nil
}
// logrus log to file
file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatal(err)
}
log.SetOutput(file)
return func() {
file.Close()
}
}
func setFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(
&params.logPath,
logFlag,
"",
"the log file path",
)
cmd.Flags().IntVar(
&params.port,
portFlag,
9000,
"the api service used port",
)
cmd.Flags().StringVar(
&params.host,
hostFlag,
"0.0.0.0",
"the api service used host",
)
cmd.Flags().StringVar(
&params.downloadDir,
downloadDirFlag,
"/root/data/download",
"the download directory",
)
cmd.Flags().StringVar(
&params.caddyUrl,
caddyUrlFlag,
"",
"caddy api url",
)
cmd.Flags().StringVar(
&params.caddyRoot,
caddyRootFlag,
"",
"caddy configure root path",
)
}
func runCommand(cmd *cobra.Command, _ []string) {
closeFunc := setlog(params.logPath)
defer func() {
if closeFunc != nil {
closeFunc()
}
}()
api := openapi.NewOpenAPI(&openapi.Config{
Host: params.host,
Port: params.port,
TempDir: params.downloadDir,
})
if err := api.Run(); err != nil {
log.WithError(err).Error("api service exit")
}
}
module github.com/caddyproxy
go 1.21
require (
github.com/gin-gonic/gin v1.10.0
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.1
)
require (
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
package main
import "caddyproxy/command/root"
func main() {
root.NewRootCommand().Execute()
}
package openapi
type Config struct {
Host string
Port int
TempDir string // temp dir store download files.
}
package openapi
import (
"caddyproxy/caddy"
"caddyproxy/types"
"caddyproxy/utils"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
"net/http"
"path/filepath"
)
type apiHandler struct {
conf *Config
backend *caddy.CaddyAPI
}
func (api apiHandler) CreateWebsite(c *gin.Context) {
var req types.CreateWebsite
err := c.ShouldBindJSON(&req) // 解析req参数
if err != nil {
log.WithError(err).Error("CreateWebsite ctx.ShouldBindJSON error")
api.response(c, http.StatusBadRequest, err, nil)
}
uid := uuid.NewString()
target := filepath.Join(api.conf.TempDir, uid)
if err := utils.Download(req.Resource, target); err != nil {
log.WithError(err).Error("Download resource failed")
api.response(c, http.StatusInternalServerError, err, nil)
}
if err := api.backend.CreateWebsite(req.Domain, target); err != nil {
log.WithError(err).Error("CreateWebsite backend.CreateWebsite error")
api.response(c, http.StatusInternalServerError, err, nil)
} else {
api.response(c, http.StatusOK, nil, nil)
}
}
func (api apiHandler) ForwardWebsite(c *gin.Context) {
var req types.ForwardWebsite
err := c.ShouldBindJSON(&req) // 解析req参数
if err != nil {
log.WithError(err).Error("ForwardWebsite ctx.ShouldBindJSON error")
api.response(c, http.StatusBadRequest, err, nil)
}
if err := api.backend.ForwardWebsite(req); err != nil {
log.WithError(err).Error("ForwardWebsite backend.ForwardWebsite error")
api.response(c, http.StatusInternalServerError, err, nil)
} else {
api.response(c, http.StatusOK, nil, nil)
}
}
func (api apiHandler) response(c *gin.Context, code int, err error, data interface{}) {
result := make(map[string]interface{})
result["code"] = code
if err != nil {
result["message"] = err.Error()
}
if data != nil {
result["data"] = data
}
c.JSON(code, result)
}
package openapi
import (
"fmt"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
type OpenAPI struct {
conf *Config
}
func NewOpenAPI(conf *Config) *OpenAPI {
return &OpenAPI{conf: conf}
}
func (s *OpenAPI) Run() error {
return s.startHttp(fmt.Sprintf("%s:%d", s.conf.Host, s.conf.Port))
}
func (s *OpenAPI) startHttp(address string) error {
router := gin.Default()
router.Use(cors())
router.Use(ginLogrus())
// 创建v1组
v1 := router.Group("/v1")
{
v1.POST("/create-website", apiHandler{}.CreateWebsite)
v1.POST("/forward-website", apiHandler{}.ForwardWebsite)
}
return router.Run(address)
}
// gin use logrus
func ginLogrus() gin.HandlerFunc {
return func(c *gin.Context) {
log.WithFields(log.Fields{
"method": c.Request.Method,
"path": c.Request.URL.Path,
"query": c.Request.URL.RawQuery,
"ip": c.ClientIP(),
}).Info("request")
c.Next()
}
}
// enable cors
func cors() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Max-Age", "86400")
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Origin, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
c.Writer.Header().Set("Access-Control-Expose-Headers", "Content-Length")
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(200)
} else {
c.Next()
}
}
}
package types
type CreateWebsite struct {
Domain string `json:"domain"`
Resource string `json:"resource"` // Resource url, it is a zip file, eg. http://example.com/website.zip
}
type ForwardWebsite struct {
Domain string `json:"domain"`
Target string `json:"target"`
}
package utils
import (
"io"
"net/http"
"os"
)
func Download(url string, target string) error {
// download the resource from the URL and save it to the target path.
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
out, err := os.Create(target)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, resp.Body)
return err
}
package utils
import "testing"
func TestDownload(t *testing.T) {
url := ""
target := "./"
err := Download(url, target)
if err != nil {
t.Error(err)
}
}
package utils
import "os"
func CreateFile(path string, content string) error {
return os.WriteFile(path, []byte(content), 0644)
}
package utils
import (
"archive/zip"
"errors"
"fmt"
log "github.com/sirupsen/logrus"
"io"
"os"
"path/filepath"
"strings"
)
// unzip unzip zip file to dest directory.
func Unzip(src string, dest string) error {
r, err := zip.OpenReader(src)
if err != nil {
return errors.New(fmt.Sprintf("open zip file failed: %s", err))
}
defer r.Close()
// if dest directory not exists, create it.
if _, err := os.Stat(dest); os.IsNotExist(err) {
if err := os.MkdirAll(dest, os.ModePerm); err != nil {
return errors.New(fmt.Sprintf("create dest directory failed: %s", err))
}
}
for _, f := range r.File {
fpath := filepath.Join(dest, f.Name)
// 防止 ZipSlip 漏洞
if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
log.WithField("fpath", fpath).Error("invalid file path, ignore to unzip")
continue
}
if f.FileInfo().IsDir() {
os.MkdirAll(fpath, os.ModePerm)
continue
}
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
return errors.New(fmt.Sprintf("create file directory failed: %s", err))
}
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
rc, err := f.Open()
if err != nil {
return err
}
_, err = io.Copy(outFile, rc)
outFile.Close()
rc.Close()
if err != nil {
return err
}
}
return nil
}
package utils
import "testing"
func TestUnzip(t *testing.T) {
f := "a.zip"
d := "a"
err := Unzip(f, d)
if err != nil {
t.Error(err)
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment