/
golang_server.go
176 lines (146 loc) · 5.25 KB
/
golang_server.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
package http_ns
import (
"encoding/pem"
"errors"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
"time"
fsutil "github.com/go-git/go-billy/v5/util"
"github.com/inoxlang/inox/internal/core"
"github.com/inoxlang/inox/internal/inoxconsts"
"github.com/inoxlang/inox/internal/utils"
)
const (
DEFAULT_SELF_SIGNED_CERT_VALIDITY_DURATION = time.Hour * 24 * 180
RELATIVE_SELF_SIGNED_CERT_FILEPATH = "./" + inoxconsts.DEV_DIR_NAME + "/self_signed.cert"
RELATIVE_SELF_SIGNED_CERT_KEY_FILEPATH = "./" + inoxconsts.DEV_DIR_NAME + "/self_signed.key"
)
type GolangHttpServerConfig struct {
//hostname:port or :port
Addr string
Handler http.Handler
PemEncodedCert string
PemEncodedKey string
AllowSelfSignedCertCreationEvenIfExposed bool
//if true the certificate and key files are persisted on the filesystem for later reuse.
PersistCreatedLocalCert bool
SelfSignedCertValidityDuration time.Duration //defaults to DEFAULT_SELF_SIGNED_CERT_VALIDITY_DURATION
ReadHeaderTimeout time.Duration // defaults to DEFAULT_HTTP_SERVER_READ_HEADER_TIMEOUT
ReadTimeout time.Duration // defaults to DEFAULT_HTTP_SERVER_READ_TIMEOUT
WriteTimeout time.Duration // defaults to DEFAULT_HTTP_SERVER_WRITE_TIMEOUT
MaxHeaderBytes int // defaults to DEFAULT_HTTP_SERVER_MAX_HEADER_BYTES
}
func NewGolangHttpServer(ctx *core.Context, config GolangHttpServerConfig) (*http.Server, error) {
fls := ctx.GetFileSystem()
pemEncodedCert := config.PemEncodedCert
pemEncodedKey := config.PemEncodedKey
//if no certificate is provided by the user we create one
if config.PemEncodedCert == "" && (isLocalhostOr127001Addr(config.Addr) || (isBindAllAddress(config.Addr) && config.AllowSelfSignedCertCreationEvenIfExposed)) {
initialWorkingDir := ctx.InitialWorkingDirectory()
var (
CERT_FILEPATH = initialWorkingDir.Join(RELATIVE_SELF_SIGNED_CERT_FILEPATH, fls).UnderlyingString()
CERT_KEY_FILEPATH = initialWorkingDir.Join(RELATIVE_SELF_SIGNED_CERT_KEY_FILEPATH, fls).UnderlyingString()
)
validityDuration := utils.DefaultIfZero(config.SelfSignedCertValidityDuration, DEFAULT_SELF_SIGNED_CERT_VALIDITY_DURATION)
generateCert := false
if config.PersistCreatedLocalCert {
certFileStat, err1 := fls.Stat(CERT_FILEPATH)
_, err2 := fls.Stat(CERT_KEY_FILEPATH)
if errors.Is(err1, os.ErrNotExist) || errors.Is(err2, os.ErrNotExist) || time.Since(certFileStat.ModTime()) >= validityDuration {
//generate a new certificate if at least one of the file does not exist or the certificate is no longer valid.
generateCert = true
if err1 == nil {
fls.Remove(CERT_FILEPATH)
}
if err2 == nil {
fls.Remove(CERT_KEY_FILEPATH)
}
} else if err1 == nil && err2 == nil {
//reuse
certFile, err := fsutil.ReadFile(fls, CERT_FILEPATH)
if err != nil {
return nil, err
}
keyFile, err := fsutil.ReadFile(fls, CERT_KEY_FILEPATH)
if err != nil {
return nil, err
}
pemEncodedCert = string(certFile)
pemEncodedKey = string(keyFile)
} else {
return nil, fmt.Errorf("%w %w", err1, err2)
}
} else {
generateCert = true
}
if generateCert {
cert, key, err := GenerateSelfSignedCertAndKey(SelfSignedCertParams{
Localhost: true,
NonLocalhostIPs: config.AllowSelfSignedCertCreationEvenIfExposed,
ValidityDuration: DEFAULT_SELF_SIGNED_CERT_VALIDITY_DURATION,
})
if err != nil {
return nil, err
}
pemEncodedCert = string(pem.EncodeToMemory(cert))
pemEncodedKey = string(pem.EncodeToMemory(key))
if config.PersistCreatedLocalCert {
dir := filepath.Dir(CERT_FILEPATH)
err := fls.MkdirAll(dir, 0700)
if err != nil {
goto ignore_writes
}
certFile, err := fls.Create(CERT_FILEPATH)
if err != nil {
//landlock
goto ignore_writes
}
pem.Encode(certFile, cert)
certFile.Close()
keyFile, err := fls.Create(CERT_KEY_FILEPATH)
if err != nil {
goto ignore_writes
}
pem.Encode(keyFile, key)
keyFile.Close()
}
ignore_writes:
}
}
if pemEncodedCert == "" {
return nil, errors.New("no certificate was provided and no self-signed certificate was generated")
}
tlsConfig, err := GetTLSConfig(ctx, pemEncodedCert, pemEncodedKey)
if err != nil {
return nil, fmt.Errorf("failed to get TLS config: %w", err)
}
server := &http.Server{
Addr: config.Addr,
Handler: config.Handler,
ReadHeaderTimeout: utils.DefaultIfZero(config.ReadHeaderTimeout, DEFAULT_HTTP_SERVER_READ_HEADER_TIMEOUT),
ReadTimeout: utils.DefaultIfZero(config.ReadTimeout, DEFAULT_HTTP_SERVER_READ_TIMEOUT),
WriteTimeout: utils.DefaultIfZero(config.WriteTimeout, DEFAULT_HTTP_SERVER_WRITE_TIMEOUT),
MaxHeaderBytes: utils.DefaultIfZero(config.MaxHeaderBytes, DEFAULT_HTTP_SERVER_MAX_HEADER_BYTES),
TLSConfig: tlsConfig,
//TODO: set logger
}
return server, nil
}
func isLocalhostOr127001Addr[S ~string](addr S) bool {
if addr == "localhost" || addr == "127.0.0.1" {
return true
}
return strings.HasPrefix(string(addr), "localhost:") || strings.HasPrefix(string(addr), "127.0.0.1:")
}
func isBindAllAddress[S ~string](addr S) bool {
if addr == "" {
return false
}
if addr == "0.0.0.0" || addr[0] == ':' {
return true
}
return strings.HasPrefix(string(addr), "0.0.0.0:")
}