forked from harshavardhana/s3www
-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
189 lines (169 loc) · 5.12 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
package main
import (
"context"
"flag"
"log"
"net"
"net/http"
"net/url"
"os"
"strings"
"time"
"github.com/caddyserver/certmagic"
minio "github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/minio/minio-go/v7/pkg/s3utils"
)
// S3 - A S3 implements FileSystem using the minio client
// allowing access to your S3 buckets and objects.
//
// Note that S3 will allow all access to files in your private
// buckets, If you have any sensitive information please make
// sure to not sure this project.
type S3 struct {
*minio.Client
bucket string
}
// Open - implements http.Filesystem implementation.
func (s3 *S3) Open(name string) (http.File, error) {
if strings.HasSuffix(name, pathSeparator) {
return &httpMinioObject{
client: s3.Client,
object: nil,
isDir: true,
bucket: bucket,
prefix: strings.TrimSuffix(name, pathSeparator),
}, nil
}
name = strings.TrimPrefix(name, pathSeparator)
obj, err := getObject(context.Background(), s3, name)
if err != nil {
return nil, os.ErrNotExist
}
return &httpMinioObject{
client: s3.Client,
object: obj,
isDir: false,
bucket: bucket,
prefix: name,
}, nil
}
func getObject(ctx context.Context, s3 *S3, name string) (*minio.Object, error) {
names := [4]string{name, name + "/index.html", name + "/index.htm", "/404.html"}
for _, n := range names {
obj, err := s3.Client.GetObject(ctx, s3.bucket, n, minio.GetObjectOptions{})
if err != nil {
log.Println(err)
continue
}
_, err = obj.Stat()
if err != nil {
// do not log "file" in bucket not found errors
if minio.ToErrorResponse(err).Code != "NoSuchKey" {
log.Println(err)
}
continue
}
return obj, nil
}
return nil, os.ErrNotExist
}
var (
endpoint string
accessKey string
secretKey string
address string
bucket string
tlsCert string
tlsKey string
letsEncrypt bool
)
func init() {
flag.StringVar(&endpoint, "endpoint", "", "S3 server endpoint")
flag.StringVar(&accessKey, "accessKey", "", "Access key of S3 storage")
flag.StringVar(&secretKey, "secretKey", "", "Secret key of S3 storage")
flag.StringVar(&bucket, "bucket", "", "Bucket name which hosts static files")
flag.StringVar(&address, "address", "127.0.0.1:8080", "Bind to a specific ADDRESS:PORT, ADDRESS can be an IP or hostname")
flag.StringVar(&tlsCert, "ssl-cert", "", "TLS certificate for this server")
flag.StringVar(&tlsKey, "ssl-key", "", "TLS private key for this server")
flag.BoolVar(&letsEncrypt, "lets-encrypt", false, "Enable Let's Encrypt")
}
// NewCustomHTTPTransport returns a new http configuration
// used while communicating with the cloud backends.
// This sets the value for MaxIdleConnsPerHost from 2 (go default)
// to 100.
func NewCustomHTTPTransport() *http.Transport {
return &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
MaxIdleConns: 1024,
MaxIdleConnsPerHost: 1024,
IdleConnTimeout: 30 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
DisableCompression: true,
}
}
func main() {
flag.Parse()
if strings.TrimSpace(bucket) == "" {
log.Fatalln(`Bucket name cannot be empty, please provide 's3www -bucket "mybucket"'`)
}
u, err := url.Parse(endpoint)
if err != nil {
log.Fatalln(err)
}
// Chains all credential types, in the following order:
// - AWS env vars (i.e. AWS_ACCESS_KEY_ID)
// - AWS creds file (i.e. AWS_SHARED_CREDENTIALS_FILE or ~/.aws/credentials)
// - IAM profile based credentials. (performs an HTTP
// call to a pre-defined endpoint, only valid inside
// configured ec2 instances)
var defaultAWSCredProviders = []credentials.Provider{
&credentials.EnvAWS{},
&credentials.FileAWSCredentials{},
&credentials.IAM{
Client: &http.Client{
Transport: NewCustomHTTPTransport(),
},
},
&credentials.EnvMinio{},
}
if accessKey != "" && secretKey != "" {
defaultAWSCredProviders = []credentials.Provider{
&credentials.Static{
Value: credentials.Value{
AccessKeyID: accessKey,
SecretAccessKey: secretKey,
},
},
}
}
// If we see an Amazon S3 endpoint, then we use more ways to fetch backend credentials.
// Specifically IAM style rotating credentials are only supported with AWS S3 endpoint.
creds := credentials.NewChainCredentials(defaultAWSCredProviders)
client, err := minio.New(u.Host, &minio.Options{
Creds: creds,
Secure: u.Scheme == "https",
Region: s3utils.GetRegionFromURL(*u),
BucketLookup: minio.BucketLookupAuto,
Transport: NewCustomHTTPTransport(),
})
if err != nil {
log.Fatalln(err)
}
mux := http.FileServer(&S3{client, bucket})
if letsEncrypt {
log.Printf("Started listening on https://%s\n", address)
certmagic.HTTPS([]string{address}, mux)
} else if tlsCert != "" && tlsKey != "" {
log.Printf("Started listening on https://%s\n", address)
log.Fatalln(http.ListenAndServeTLS(address, tlsCert, tlsKey, mux))
} else {
log.Printf("Started listening on http://%s\n", address)
log.Fatalln(http.ListenAndServe(address, mux))
}
}