mirror of
https://github.com/fumiama/imoto.git
synced 2026-06-27 07:20:24 +08:00
v0.1.0
This commit is contained in:
62
.github/workflows/release.yml
vendored
Normal file
62
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
name: 发行版
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
|
||||
jobs:
|
||||
my-job:
|
||||
name: Build imoto on Push Tag 🚀
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '1.21'
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Cache Go
|
||||
id: cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
# A list of files, directories, and wildcard patterns to cache and restore
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-build-${{ hashFiles('**/go.sum') }}
|
||||
|
||||
- name: Tidy Go modules
|
||||
run: go mod tidy
|
||||
|
||||
- name: Build linux-x64
|
||||
run: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o artifacts/imoto-linux-x64
|
||||
- name: Build linux-x86
|
||||
run: CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags="-s -w" -trimpath -o artifacts/imoto-linux-x86
|
||||
|
||||
- name: Build windows-x64
|
||||
run: CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o artifacts/imoto-windows-x64.exe
|
||||
- name: Build windows-x86
|
||||
run: CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags="-s -w" -trimpath -o artifacts/imoto-windows-x86.exe
|
||||
|
||||
- name: Build linux-arm64
|
||||
run: CGO_ENABLED=0 GOOS=linux GOARCH=arm64 GOARM=7 go build -ldflags="-s -w" -trimpath -o artifacts/imoto-linux-arm64
|
||||
- name: Build linux-armhfv6
|
||||
run: CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build -ldflags="-s -w" -trimpath -o artifacts/imoto-linux-armhfv6
|
||||
|
||||
- name: Build darwin-x64
|
||||
run: CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o artifacts/imoto-darwin-x64
|
||||
- name: Build darwin-arm64
|
||||
run: CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -trimpath -o artifacts/imoto-darwin-arm64
|
||||
|
||||
- name: Upload binaries to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: artifacts/imoto-*
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
97
cmd/main.go
Normal file
97
cmd/main.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"flag"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/FloatTech/ttl"
|
||||
"github.com/fumiama/imgsz"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/fumiama/imoto"
|
||||
)
|
||||
|
||||
var imgcache *ttl.Cache[uint64, *imagebody]
|
||||
|
||||
type imagebody struct {
|
||||
key uint64
|
||||
typ string
|
||||
dat []byte
|
||||
}
|
||||
|
||||
func main() {
|
||||
cachetime := flag.Uint("t", 60, "cache time (s)")
|
||||
endpoint := flag.String("e", "127.0.0.1:8000", "listening endpoint")
|
||||
flag.Parse()
|
||||
imgcache = ttl.NewCache[uint64, *imagebody](time.Second * time.Duration(*cachetime))
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
m, err := imoto.GetMD5(r.URL.Path)
|
||||
defer r.Body.Close()
|
||||
if err != nil {
|
||||
http.Error(w, "400 Bad Request: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
p, k := imoto.SplitMD5(m)
|
||||
logrus.Infoln("[handle]", r.Method, r.URL.Path)
|
||||
switch r.Method {
|
||||
case http.MethodHead:
|
||||
if imgcache.Get(p) == nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
case http.MethodGet:
|
||||
img := imgcache.Get(p)
|
||||
if img == nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Header().Set("Content-Type", "image/"+img.typ)
|
||||
_, _ = w.Write(img.dat)
|
||||
case http.MethodPut:
|
||||
data, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "500 Internal Server Error: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
realm := md5.Sum(data)
|
||||
if m != realm {
|
||||
http.Error(w, "400 Bad Request: file md5 mismatch", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
_, typ, err := imgsz.DecodeSize(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
http.Error(w, "400 Bad Request: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
imgcache.Set(p, &imagebody{
|
||||
key: k,
|
||||
typ: typ,
|
||||
dat: data,
|
||||
})
|
||||
w.WriteHeader(http.StatusOK)
|
||||
case http.MethodDelete:
|
||||
img := imgcache.Get(p)
|
||||
if img == nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if k != img.key {
|
||||
http.Error(w, "403 Forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
imgcache.Delete(p)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Header().Set("Content-Type", "image/"+img.typ)
|
||||
_, _ = w.Write(img.dat)
|
||||
default:
|
||||
http.Error(w, "405 Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
})
|
||||
logrus.Errorln(http.ListenAndServe(*endpoint, nil))
|
||||
}
|
||||
11
go.mod
Normal file
11
go.mod
Normal file
@@ -0,0 +1,11 @@
|
||||
module github.com/fumiama/imoto
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/FloatTech/ttl v0.0.0-20230307105452-d6f7b2b647d1
|
||||
github.com/fumiama/imgsz v0.0.2
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
)
|
||||
|
||||
require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
|
||||
19
go.sum
Normal file
19
go.sum
Normal file
@@ -0,0 +1,19 @@
|
||||
github.com/FloatTech/ttl v0.0.0-20230307105452-d6f7b2b647d1 h1:g4pTnDJUW4VbJ9NvoRfUvdjDrHz/6QhfN/LoIIpICbo=
|
||||
github.com/FloatTech/ttl v0.0.0-20230307105452-d6f7b2b647d1/go.mod h1:fHZFWGquNXuHttu9dUYoKuNbm3dzLETnIOnm1muSfDs=
|
||||
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/fumiama/imgsz v0.0.2 h1:fAkC0FnIscdKOXwAxlyw3EUba5NzxZdSxGaq3Uyfxak=
|
||||
github.com/fumiama/imgsz v0.0.2/go.mod h1:dR71mI3I2O5u6+PCpd47M9TZptzP+39tRBcbdIkoqM4=
|
||||
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/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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=
|
||||
60
helper.go
Normal file
60
helper.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package imoto
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func GetMD5(u string) (m [md5.Size]byte, err error) {
|
||||
u = strings.Trim(u, "/ ?&\n\t")
|
||||
if len(u) != md5.Size*2 && len(u) != md5.Size {
|
||||
err = errors.New("invalid path len: " + strconv.Itoa(len(u)))
|
||||
return
|
||||
}
|
||||
_, err = hex.Decode(m[:], StringToBytes(u))
|
||||
return
|
||||
}
|
||||
|
||||
func SplitMD5(m [md5.Size]byte) (path uint64, key uint64) {
|
||||
path = binary.LittleEndian.Uint64(m[:8])
|
||||
key = binary.LittleEndian.Uint64(m[8:])
|
||||
return
|
||||
}
|
||||
|
||||
func Uint64String(x uint64) string {
|
||||
var buf [8]byte
|
||||
binary.LittleEndian.PutUint64(buf[:], x)
|
||||
return hex.EncodeToString(buf[:])
|
||||
}
|
||||
|
||||
// slice is the runtime representation of a slice.
|
||||
// It cannot be used safely or portably and its representation may
|
||||
// change in a later release.
|
||||
//
|
||||
// Unlike reflect.SliceHeader, its Data field is sufficient to guarantee the
|
||||
// data it references will not be garbage collected.
|
||||
type slice struct {
|
||||
data unsafe.Pointer
|
||||
len int
|
||||
cap int
|
||||
}
|
||||
|
||||
// BytesToString 没有内存开销的转换
|
||||
func BytesToString(b []byte) string {
|
||||
return *(*string)(unsafe.Pointer(&b))
|
||||
}
|
||||
|
||||
// StringToBytes 没有内存开销的转换
|
||||
func StringToBytes(s string) (b []byte) {
|
||||
bh := (*slice)(unsafe.Pointer(&b))
|
||||
sh := (*slice)(unsafe.Pointer(&s))
|
||||
bh.data = sh.data
|
||||
bh.len = sh.len
|
||||
bh.cap = sh.len
|
||||
return b
|
||||
}
|
||||
91
test/main.go
Normal file
91
test/main.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/fumiama/imoto"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data, err := os.ReadFile("test.jpeg")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
m := md5.Sum(data)
|
||||
p, _ := imoto.SplitMD5(m)
|
||||
req, err := http.NewRequest("PUT", "http://127.0.0.1:8000/"+hex.EncodeToString(m[:]), bytes.NewReader(data))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
msg, _ := io.ReadAll(resp.Body)
|
||||
panic("PUT error: " + imoto.BytesToString(msg))
|
||||
}
|
||||
req, err = http.NewRequest("HEAD", "http://127.0.0.1:8000/"+imoto.Uint64String(p), nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
resp, err = http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
msg, _ := io.ReadAll(resp.Body)
|
||||
panic("HEAD error: " + imoto.BytesToString(msg))
|
||||
}
|
||||
req, err = http.NewRequest("GET", "http://127.0.0.1:8000/"+imoto.Uint64String(p), nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
resp, err = http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
msg, _ := io.ReadAll(resp.Body)
|
||||
panic("HEAD error: " + imoto.BytesToString(msg))
|
||||
}
|
||||
h := md5.New()
|
||||
_, err = io.Copy(h, resp.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var m2 [md5.Size]byte
|
||||
h.Sum(m2[:0])
|
||||
if m2 != m {
|
||||
panic("GET error: expected " + hex.EncodeToString(m[:]) + " but got " + hex.EncodeToString(m2[:]))
|
||||
}
|
||||
req, err = http.NewRequest("DELETE", "http://127.0.0.1:8000/"+hex.EncodeToString(m[:]), nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
resp, err = http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
msg, _ := io.ReadAll(resp.Body)
|
||||
panic("HEAD error: " + imoto.BytesToString(msg))
|
||||
}
|
||||
h = md5.New()
|
||||
_, err = io.Copy(h, resp.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
h.Sum(m2[:0])
|
||||
if m2 != m {
|
||||
panic("DELETE error: expected " + hex.EncodeToString(m[:]) + " but got " + hex.EncodeToString(m2[:]))
|
||||
}
|
||||
}
|
||||
BIN
test/test.jpeg
Normal file
BIN
test/test.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
Reference in New Issue
Block a user