Nginx后置的Golang对象存储代理实现
Nginx后置的Golang对象存储代理实现 我正在开发一个私人图片托管网站。为了支持扩展性,我切换到了兼容S3的对象存储服务。存储桶是私有的。主Web应用使用Laravel构建。我打算用以下代码代理图片请求。这个Go应用将部署在nginx后面。
如果有人能帮忙查看下面的代码,并告诉我是否有任何提升性能的方法,我将不胜感激。如果您有任何建议,我也非常乐意听取。谢谢!
package goos
import (
"io"
"net/http"
"net/url"
"strconv"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
)
// Logger interface
type Logger interface {
Print(string)
}
// Goos holds the information to connect to an S3 compatible Object Storage Service
type Goos struct {
KeyID string
Secret string
Endpoint string
Region string
Bucket string
Logger
}
func (g *Goos) session() *session.Session {
s3Config := &aws.Config{
Credentials: credentials.NewStaticCredentials(g.KeyID, g.Secret, ""),
Endpoint: aws.String(g.Endpoint),
Region: aws.String(g.Region),
}
sess := session.New(s3Config)
return sess
}
// Handler will return the handler we need
func (g *Goos) Handler() http.HandlerFunc {
svc := s3.New(g.session())
fn := func(w http.ResponseWriter, r *http.Request) {
cf := r.Header.Get("X-Real-Ip")
ip := r.Header.Get("X-Forwarded-For")
if r.URL.String() == "/" {
g.logMessage(cf, ip, r.URL.String(), "/")
notFound(w)
return
}
url, err := url.QueryUnescape(r.URL.String())
if err != nil {
g.logMessage(cf, ip, r.URL.String(), "404")
notFound(w)
return
}
input := &s3.GetObjectInput{
Bucket: aws.String(g.Bucket),
Key: aws.String(url),
}
result, err := svc.GetObject(input)
if err != nil {
g.logMessage(cf, ip, url, "404")
notFound(w)
return
}
defer result.Body.Close()
w.Header().Set("Content-Length", strconv.FormatInt(*result.ContentLength, 10))
w.Header().Set("Last-Modified", result.LastModified.Format("Mon, 02 Jan 2006 15:04:05 MST"))
w.Header().Set("Expires", time.Now().AddDate(60, 0, 0).Format(http.TimeFormat))
w.Header().Set("Cache-Control", "max-age:290304000")
w.Header().Set("Etag", *result.ETag)
_, err = io.Copy(w, result.Body)
if err != nil {
g.logMessage(cf, ip, url, "500")
notFound(w)
return
}
g.logMessage(cf, ip, url, "200")
}
return http.HandlerFunc(fn)
}
func notFound(w http.ResponseWriter) {
w.Header().Set("Cache-Control", "max-age:0, private")
w.WriteHeader(http.StatusNotFound)
w.Write([]byte("Not Found."))
}
func (g *Goos) logMessage(cf string, ip string, url string, status string) {
if g.Logger == nil {
return
}
g.Print("[" + cf + "|" + ip + "] " + "[" + url + "] " + "[" + status + "]")
}
更多关于Nginx后置的Golang对象存储代理实现的实战教程也可以访问 https://www.itying.com/category-94-b0.html
1 回复
更多关于Nginx后置的Golang对象存储代理实现的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
以下是针对您提供的Go代码的性能优化建议和实现改进。代码本身已经实现了基本功能,但可以通过几个关键点来提升性能和可靠性。
1. 会话和S3客户端复用
当前代码在每次请求时都会创建新的session和S3客户端,这会带来不必要的开销。建议在初始化时创建并复用这些对象:
type Goos struct {
KeyID string
Secret string
Endpoint string
Region string
Bucket string
Logger
svc *s3.S3
}
func NewGoos(keyID, secret, endpoint, region, bucket string, logger Logger) *Goos {
g := &Goos{
KeyID: keyID,
Secret: secret,
Endpoint: endpoint,
Region: region,
Bucket: bucket,
Logger: logger,
}
s3Config := &aws.Config{
Credentials: credentials.NewStaticCredentials(keyID, secret, ""),
Endpoint: aws.String(endpoint),
Region: aws.String(region),
}
sess := session.Must(session.NewSession(s3Config))
g.svc = s3.New(sess)
return g
}
// Handler will return the handler we need
func (g *Goos) Handler() http.HandlerFunc {
fn := func(w http.ResponseWriter, r *http.Request) {
// 使用预初始化的g.svc
input := &s3.GetObjectInput{
Bucket: aws.String(g.Bucket),
Key: aws.String(r.URL.Path),
}
result, err := g.svc.GetObject(input)
// ... 其余代码保持不变
}
return http.HandlerFunc(fn)
}
2. 改进URL处理和错误处理
当前代码使用r.URL.String()获取路径,但这会包含查询参数。建议使用r.URL.Path并添加路径清理:
fn := func(w http.ResponseWriter, r *http.Request) {
cf := r.Header.Get("X-Real-Ip")
ip := r.Header.Get("X-Forwarded-For")
// 清理路径,移除开头的斜杠和进行URL解码
path := strings.TrimPrefix(r.URL.Path, "/")
if path == "" {
g.logMessage(cf, ip, "/", "404")
notFound(w)
return
}
// URL解码
decodedPath, err := url.PathUnescape(path)
if err != nil {
g.logMessage(cf, ip, path, "400")
http.Error(w, "Bad Request", http.StatusBadRequest)
return
}
input := &s3.GetObjectInput{
Bucket: aws.String(g.Bucket),
Key: aws.String(decodedPath),
}
result, err := g.svc.GetObject(input)
if err != nil {
g.logMessage(cf, ip, decodedPath, "404")
notFound(w)
return
}
defer result.Body.Close()
// 设置正确的缓存控制头
w.Header().Set("Content-Length", strconv.FormatInt(*result.ContentLength, 10))
w.Header().Set("Content-Type", aws.StringValue(result.ContentType))
w.Header().Set("Last-Modified", result.LastModified.Format(time.RFC1123))
w.Header().Set("Cache-Control", "public, max-age=290304000, immutable")
w.Header().Set("ETag", aws.StringValue(result.ETag))
// 处理条件请求(If-None-Match)
if match := r.Header.Get("If-None-Match"); match != "" {
if match == aws.StringValue(result.ETag) {
w.WriteHeader(http.StatusNotModified)
g.logMessage(cf, ip, decodedPath, "304")
return
}
}
_, err = io.Copy(w, result.Body)
if err != nil {
g.logMessage(cf, ip, decodedPath, "500")
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
g.logMessage(cf, ip, decodedPath, "200")
}
3. 添加连接池和超时配置
在初始化AWS会话时配置HTTP客户端以优化连接管理:
func NewGoos(keyID, secret, endpoint, region, bucket string, logger Logger) *Goos {
g := &Goos{
KeyID: keyID,
Secret: secret,
Endpoint: endpoint,
Region: region,
Bucket: bucket,
Logger: logger,
}
httpClient := &http.Client{
Timeout: 30 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 90 * time.Second,
},
}
s3Config := &aws.Config{
Credentials: credentials.NewStaticCredentials(keyID, secret, ""),
Endpoint: aws.String(endpoint),
Region: aws.String(region),
HTTPClient: httpClient,
S3ForcePathStyle: aws.Bool(true),
}
sess := session.Must(session.NewSession(s3Config))
g.svc = s3.New(sess)
return g
}
4. 改进内容类型处理
确保正确设置Content-Type头:
// 在成功获取对象后
contentType := "application/octet-stream"
if result.ContentType != nil {
contentType = *result.ContentType
}
w.Header().Set("Content-Type", contentType)
这些优化将显著提升代理的性能和可靠性,特别是在高并发场景下。主要改进包括连接复用、更好的错误处理、条件请求支持和优化的HTTP客户端配置。

