Compare commits
No commits in common. "857eee205ce3f7598a036a54ae8729fcc1727b9e" and "46b2bb796f88823019fd3d0091178565aed83a54" have entirely different histories.
857eee205c
...
46b2bb796f
11 changed files with 159 additions and 249 deletions
|
|
@ -5,11 +5,11 @@
|
|||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
basic:
|
||||
docker:
|
||||
runs-on: any
|
||||
steps:
|
||||
- name: Checkout code
|
||||
|
|
@ -45,15 +45,6 @@ jobs:
|
|||
push: true
|
||||
pull: true
|
||||
tags: git.ronmi.tw/ronmi/forgejo-pages:arm64,ronmi/forgejo-pages:arm64
|
||||
- name: Build arm64 image (git)
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: git.dockerfile
|
||||
platforms: linux/arm64
|
||||
push: true
|
||||
pull: true
|
||||
tags: git.ronmi.tw/ronmi/forgejo-pages:git-arm64,ronmi/forgejo-pages:git-arm64
|
||||
- name: Build amd64 binary
|
||||
run: GOARCH=amd64 go build
|
||||
- name: Build amd64 image
|
||||
|
|
@ -64,24 +55,14 @@ jobs:
|
|||
push: true
|
||||
pull: true
|
||||
tags: git.ronmi.tw/ronmi/forgejo-pages:amd64,ronmi/forgejo-pages:amd64
|
||||
- name: Build amd64 image (git)
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: git.dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
pull: true
|
||||
tags: git.ronmi.tw/ronmi/forgejo-pages:git-amd64,ronmi/forgejo-pages:git-amd64
|
||||
- name: Create multiarch image
|
||||
run: |
|
||||
docker buildx imagetools create -t git.ronmi.tw/ronmi/forgejo-pages git.ronmi.tw/ronmi/forgejo-pages:arm64 git.ronmi.tw/ronmi/forgejo-pages:amd64
|
||||
docker buildx imagetools create -t ronmi/forgejo-pages ronmi/forgejo-pages:arm64 ronmi/forgejo-pages:amd64
|
||||
docker buildx imagetools create -t git.ronmi.tw/ronmi/forgejo-pages:git git.ronmi.tw/ronmi/forgejo-pages:git-arm64 git.ronmi.tw/ronmi/forgejo-pages:git-amd64
|
||||
docker buildx imagetools create -t ronmi/forgejo-pages:git ronmi/forgejo-pages:git-arm64 ronmi/forgejo-pages:git-amd64
|
||||
- name: Update readme to docker hub
|
||||
uses: https://github.com/peter-evans/dockerhub-description@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USER }}
|
||||
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
repository: ronmi/forgejo-pages
|
||||
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +0,0 @@
|
|||
forgejo-pages
|
||||
|
|
@ -14,7 +14,7 @@ This mode is good for simple setup, like, you have small number of viewers, or y
|
|||
|
||||
### Webhook mode
|
||||
|
||||
Webhook mode is a tool which helps you to download latest content via git. You'll have to setup a webhook in forgejo server in order to notify it when to download new content. You have to have `git` binary in your path.
|
||||
Webhook mode is a tool which helps you to download latest content via git. You'll have to setup a webhook in forgejo server in order to notify it when to download new content.
|
||||
|
||||
To serve downloaded pages, you'll have to use a web server like Nginx.
|
||||
|
||||
|
|
@ -41,13 +41,9 @@ Take care about permissions of the API token. For serve mode, repositories the k
|
|||
```
|
||||
docker run -p 8080:8080 --user 1000:1000 ronmi/forgejo-pages serve -s https://git.example.com -k my-secret-token
|
||||
|
||||
docker run -p 8080:8080 --user 1000:1000 -v `pwd`/data:/data ronmi/forgejo-pages:git listen -u myuser -k my-secret-token -s https://git.example.com -a :8080 -b static-pages -d /data
|
||||
docker run -p 8080:8080 --user 1000:1000 -v `pwd`/data:/data ronmi/forgejo-pages listen -u myuser -k my-secret-token -s https://git.example.com -a :8080 -b static-pages -d /data
|
||||
```
|
||||
|
||||
Serve mode could use `latest` tag, a minimal image contains only libc, CA certificates, timezone data and page server binary.
|
||||
|
||||
Webhook mode should use `git` tag.
|
||||
|
||||
# FAQ
|
||||
|
||||
### Can I use user.example.com/repo/path format?
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ import (
|
|||
"time"
|
||||
|
||||
"git.ronmi.tw/ronmi/forgejo-pages/lib"
|
||||
"github.com/raohwork/task"
|
||||
"github.com/raohwork/task/httptask"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
|
@ -70,7 +68,7 @@ is up to you, eg. Nginx, Apache, or even a simple Go server.
|
|||
GitPass: token,
|
||||
}
|
||||
|
||||
s, r, err := lib.UseWebhook(bind, cfg)
|
||||
s, err := lib.UseWebhook(bind, cfg)
|
||||
if err != nil {
|
||||
fmt.Println("cannot create server: ", err)
|
||||
return
|
||||
|
|
@ -85,11 +83,14 @@ is up to you, eg. Nginx, Apache, or even a simple Go server.
|
|||
defer stop()
|
||||
|
||||
fmt.Println("starting server")
|
||||
task.Wait(
|
||||
httptask.Server(s, task.Timeout(10*time.Second)),
|
||||
r.Run,
|
||||
).Run(ctx)
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
stop()
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
|
||||
defer cancel()
|
||||
s.Shutdown(ctx)
|
||||
}()
|
||||
s.ListenAndServe()
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
11
cmd/serve.go
11
cmd/serve.go
|
|
@ -14,8 +14,6 @@ import (
|
|||
"time"
|
||||
|
||||
"git.ronmi.tw/ronmi/forgejo-pages/lib"
|
||||
"github.com/raohwork/task"
|
||||
"github.com/raohwork/task/httptask"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
|
@ -72,7 +70,14 @@ cache/protection layer like Cloudflare in front of the server.
|
|||
defer stop()
|
||||
|
||||
fmt.Println("starting server")
|
||||
httptask.Server(s, task.Timeout(10*time.Second)).Run(ctx)
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
stop()
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
|
||||
defer cancel()
|
||||
s.Shutdown(ctx)
|
||||
}()
|
||||
s.ListenAndServe()
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
FROM debian:stable-slim
|
||||
|
||||
# Install git
|
||||
RUN apt-get update && apt-get install -y \
|
||||
ca-certificates git tzdata \
|
||||
&& apt-get clean -y && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ADD forgejo-pages /usr/bin/forgejo-pages
|
||||
ENTRYPOINT ["/usr/bin/forgejo-pages"]
|
||||
1
go.mod
1
go.mod
|
|
@ -3,7 +3,6 @@ module git.ronmi.tw/ronmi/forgejo-pages
|
|||
go 1.21.6
|
||||
|
||||
require (
|
||||
github.com/raohwork/task v0.3.0
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spf13/viper v1.19.0
|
||||
)
|
||||
|
|
|
|||
2
go.sum
2
go.sum
|
|
@ -26,8 +26,6 @@ github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h
|
|||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/raohwork/task v0.3.0 h1:4j0jT1a+f5O+g6q22o42sFEdSlYGYtgUHLbg3cF/7VE=
|
||||
github.com/raohwork/task v0.3.0/go.mod h1:QkVxY/Q/w6bW5Xjhcp8vn5qLgoS+70jUTyGueI0nzMo=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
|
|
|
|||
159
lib/git.go
159
lib/git.go
|
|
@ -1,159 +0,0 @@
|
|||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package lib
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type repoSpec struct {
|
||||
user string
|
||||
name string
|
||||
}
|
||||
|
||||
type GitRunner struct {
|
||||
ch <-chan repoSpec
|
||||
cfg *WebhookCFG
|
||||
}
|
||||
|
||||
func (g *GitRunner) Run(ctx context.Context) error {
|
||||
// graceful: exit only if all tasks are done
|
||||
for {
|
||||
select {
|
||||
case repo := <-g.ch:
|
||||
err := g.cfg.download(repo.user, repo.name)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to download repo: ", err)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *WebhookCFG) pageDir(user, repo string) string {
|
||||
return filepath.Join(c.PageDir, user, repo)
|
||||
}
|
||||
|
||||
func (c *WebhookCFG) gitDir(user, repo string) string {
|
||||
return filepath.Join(c.GitDir, user, repo)
|
||||
}
|
||||
|
||||
func (c *WebhookCFG) checkout(user, repo string) (err error) {
|
||||
pageDir := c.pageDir(user, repo)
|
||||
gitDir := c.gitDir(user, repo)
|
||||
|
||||
git := exec.Command(
|
||||
"git",
|
||||
"--git-dir", gitDir,
|
||||
"--work-tree", pageDir,
|
||||
"checkout", "origin/"+c.Branch,
|
||||
)
|
||||
output, err := git.CombinedOutput()
|
||||
if err != nil {
|
||||
fmt.Println("git checkout failed: ", err)
|
||||
fmt.Println("=========== Dump output: ============")
|
||||
fmt.Println(string(output))
|
||||
fmt.Println("=====================================")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *WebhookCFG) fetch(user, repo string) (err error) {
|
||||
gitDir := c.gitDir(user, repo)
|
||||
git := exec.Command(
|
||||
"git",
|
||||
"--git-dir", gitDir,
|
||||
"fetch", "origin", c.Branch,
|
||||
)
|
||||
output, err := git.CombinedOutput()
|
||||
if err != nil {
|
||||
fmt.Println("git fetch failed: ", err)
|
||||
fmt.Println("=========== Dump output: ============")
|
||||
fmt.Println(string(output))
|
||||
fmt.Println("=====================================")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *WebhookCFG) clone(user, repo string) (err error) {
|
||||
pageDir := c.pageDir(user, repo)
|
||||
err = os.MkdirAll(filepath.Dir(pageDir), 0755)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
gitDir := c.gitDir(user, repo)
|
||||
err = os.MkdirAll(filepath.Dir(gitDir), 0700)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
uri := c.Server
|
||||
uri.Path = "/" + path.Join(user, repo) + ".git"
|
||||
uri.User = url.UserPassword(c.GitUser, c.GitPass)
|
||||
|
||||
git := exec.Command(
|
||||
"git",
|
||||
"clone",
|
||||
"-b", c.Branch,
|
||||
"--single-branch",
|
||||
"--no-tags",
|
||||
"--separate-git-dir", gitDir,
|
||||
uri.String(),
|
||||
pageDir,
|
||||
)
|
||||
output, err := git.CombinedOutput()
|
||||
if err != nil {
|
||||
fmt.Println("git clone failed: ", err)
|
||||
fmt.Println("=========== Dump output: ============")
|
||||
fmt.Println(string(output))
|
||||
fmt.Println("=====================================")
|
||||
}
|
||||
|
||||
os.Remove(filepath.Join(pageDir, ".git"))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var repoLock = &sync.Map{}
|
||||
|
||||
func (c *WebhookCFG) lock(user, repo string) func() {
|
||||
key := user + "/" + repo
|
||||
|
||||
lock, _ := repoLock.LoadOrStore(key, &sync.Mutex{})
|
||||
m := lock.(*sync.Mutex)
|
||||
m.Lock()
|
||||
return m.Unlock
|
||||
}
|
||||
|
||||
func (c *WebhookCFG) download(user, repo string) (err error) {
|
||||
unlock := c.lock(user, repo)
|
||||
defer unlock()
|
||||
fmt.Println("Pulling ", user, repo)
|
||||
|
||||
gitDir := c.gitDir(user, repo)
|
||||
if _, err = os.Stat(gitDir); os.IsNotExist(err) {
|
||||
err = c.clone(user, repo)
|
||||
} else {
|
||||
err = c.fetch(user, repo)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = c.checkout(user, repo)
|
||||
return
|
||||
}
|
||||
138
lib/hook.go
138
lib/hook.go
|
|
@ -10,7 +10,12 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type WebhookCFG struct {
|
||||
|
|
@ -19,7 +24,123 @@ type WebhookCFG struct {
|
|||
GitDir string
|
||||
GitUser string
|
||||
GitPass string
|
||||
ch chan<- repoSpec
|
||||
}
|
||||
|
||||
func (c *WebhookCFG) pageDir(user, repo string) string {
|
||||
return filepath.Join(c.PageDir, user, repo)
|
||||
}
|
||||
|
||||
func (c *WebhookCFG) gitDir(user, repo string) string {
|
||||
return filepath.Join(c.GitDir, user, repo)
|
||||
}
|
||||
|
||||
func (c *WebhookCFG) checkout(user, repo string) (err error) {
|
||||
pageDir := c.pageDir(user, repo)
|
||||
gitDir := c.gitDir(user, repo)
|
||||
|
||||
git := exec.Command(
|
||||
"git",
|
||||
"--git-dir", gitDir,
|
||||
"--work-tree", pageDir,
|
||||
"checkout", "origin/"+c.Branch,
|
||||
)
|
||||
output, err := git.CombinedOutput()
|
||||
if err != nil {
|
||||
fmt.Println("git checkout failed: ", err)
|
||||
fmt.Println("=========== Dump output: ============")
|
||||
fmt.Println(string(output))
|
||||
fmt.Println("=====================================")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *WebhookCFG) fetch(user, repo string) (err error) {
|
||||
gitDir := c.gitDir(user, repo)
|
||||
git := exec.Command(
|
||||
"git",
|
||||
"--git-dir", gitDir,
|
||||
"fetch", "origin", c.Branch,
|
||||
)
|
||||
output, err := git.CombinedOutput()
|
||||
if err != nil {
|
||||
fmt.Println("git fetch failed: ", err)
|
||||
fmt.Println("=========== Dump output: ============")
|
||||
fmt.Println(string(output))
|
||||
fmt.Println("=====================================")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *WebhookCFG) clone(user, repo string) (err error) {
|
||||
pageDir := c.pageDir(user, repo)
|
||||
err = os.MkdirAll(filepath.Dir(pageDir), 0755)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
gitDir := c.gitDir(user, repo)
|
||||
err = os.MkdirAll(filepath.Dir(gitDir), 0700)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
uri := c.Server
|
||||
uri.Path = "/" + path.Join(user, repo) + ".git"
|
||||
uri.User = url.UserPassword(c.GitUser, c.GitPass)
|
||||
|
||||
git := exec.Command(
|
||||
"git",
|
||||
"clone",
|
||||
"-b", c.Branch,
|
||||
"--single-branch",
|
||||
"--no-tags",
|
||||
"--separate-git-dir", gitDir,
|
||||
uri.String(),
|
||||
pageDir,
|
||||
)
|
||||
output, err := git.CombinedOutput()
|
||||
if err != nil {
|
||||
fmt.Println("git clone failed: ", err)
|
||||
fmt.Println("=========== Dump output: ============")
|
||||
fmt.Println(string(output))
|
||||
fmt.Println("=====================================")
|
||||
}
|
||||
|
||||
os.Remove(filepath.Join(pageDir, ".git"))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var repoLock = &sync.Map{}
|
||||
|
||||
func (c *WebhookCFG) lock(user, repo string) func() {
|
||||
key := user + "/" + repo
|
||||
|
||||
lock, _ := repoLock.LoadOrStore(key, &sync.Mutex{})
|
||||
m := lock.(*sync.Mutex)
|
||||
m.Lock()
|
||||
return m.Unlock
|
||||
}
|
||||
|
||||
func (c *WebhookCFG) download(user, repo string) (err error) {
|
||||
unlock := c.lock(user, repo)
|
||||
defer unlock()
|
||||
|
||||
gitDir := c.gitDir(user, repo)
|
||||
if _, err = os.Stat(gitDir); os.IsNotExist(err) {
|
||||
err = c.clone(user, repo)
|
||||
} else {
|
||||
err = c.fetch(user, repo)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = c.checkout(user, repo)
|
||||
return
|
||||
}
|
||||
|
||||
type webhookPayload struct {
|
||||
|
|
@ -64,19 +185,22 @@ func (c *WebhookCFG) handle(w http.ResponseWriter, r *http.Request) {
|
|||
user, repo := path.Split(payload.Repository.FullName)
|
||||
user = user[:len(user)-1]
|
||||
go func() {
|
||||
c.ch <- repoSpec{user: user, name: repo}
|
||||
fmt.Println("Pulling ", user, repo)
|
||||
err = c.download(user, repo)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to download repository: ", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func UseWebhook(bind string, cfg *WebhookCFG) (*http.Server, *GitRunner, error) {
|
||||
func UseWebhook(bind string, cfg *WebhookCFG) (*http.Server, error) {
|
||||
if cfg == nil {
|
||||
return nil, nil, errors.New("webhook config is nil")
|
||||
return nil, errors.New("webhook config is nil")
|
||||
}
|
||||
s := &http.Server{
|
||||
Addr: bind,
|
||||
Handler: http.HandlerFunc(cfg.handle),
|
||||
}
|
||||
ch := make(chan repoSpec, 5)
|
||||
cfg.ch = ch
|
||||
return s, &GitRunner{ch: ch, cfg: cfg}, nil
|
||||
return s, nil
|
||||
}
|
||||
|
|
|
|||
21
lib/web.go
21
lib/web.go
|
|
@ -12,8 +12,6 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"mime"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Forgejo struct {
|
||||
|
|
@ -110,16 +108,6 @@ func (f *Forgejo) handle(w http.ResponseWriter, r *http.Request) {
|
|||
headers["If-Modified-Since"] = r.Header.Get("If-Modified-Since")
|
||||
headers["If-Range"] = r.Header.Get("If-Range")
|
||||
headers["Range"] = r.Header.Get("Range")
|
||||
headers["X-Forwarded-For"] = r.Header.Get("X-Forwarded-For")
|
||||
headers["X-Forwarded-Host"] = r.Header.Get("X-Forwarded-Host")
|
||||
headers["X-Forwarded-Proto"] = r.Header.Get("X-Forwarded-Proto")
|
||||
headers["X-Real-IP"] = r.Header.Get("X-Real-IP")
|
||||
headers["X-Host"] = r.Header.Get("X-Host")
|
||||
headers["CF-Connecting-IP"] = r.Header.Get("CF-Connecting-IP")
|
||||
headers["CF-IPCountry"] = r.Header.Get("CF-IPCountry")
|
||||
headers["CF-Visitor"] = r.Header.Get("CF-Visitor")
|
||||
headers["CF-Request-ID"] = r.Header.Get("CF-Request-ID")
|
||||
headers["CF-Ray"] = r.Header.Get("CF-Ray")
|
||||
resp, err := f.GetFile(r.Context(), headers, user, repo, f.Branch, file)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
|
|
@ -131,15 +119,6 @@ func (f *Forgejo) handle(w http.ResponseWriter, r *http.Request) {
|
|||
trySet(w.Header(), "Last-Modified", resp.Header)
|
||||
trySet(w.Header(), "Content-Length", resp.Header)
|
||||
trySet(w.Header(), "Content-Range", resp.Header)
|
||||
contentType := resp.Header.Get("Content-Type")
|
||||
if contentType == "" || strings.HasPrefix(contentType, "text/plain") {
|
||||
if ct := mime.TypeByExtension(filepath.Ext(file)); ct != "" {
|
||||
contentType = ct
|
||||
}
|
||||
}
|
||||
if contentType != "" {
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
}
|
||||
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
io.Copy(w, resp.Body)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue