Commit 1df829121974bb54eb7ab678d9317eee8160736a

Authored by Marko Tikvić
Exists in master

Merge branch 'master' of ssh://git.to-net.rs:6322/marko.tikvic/webutility

File was created 1 package webutility
2
3 import (
4 "crypto/rand"
5 "fmt"
6 )
7
8 // GUID ...
9 func GUID() (string, error) {
10 b := make([]byte, 16)
11 _, err := rand.Read(b)
12 if err != nil {
13 return "", err
14 }
15 id := fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
16 return id, nil
17 }
18
middleware/main.go
1 package middleware 1 package middleware
2 2
3 import ( 3 import (
4 "fmt"
5 "io/ioutil"
4 "net/http" 6 "net/http"
7
8 web "git.to-net.rs/marko.tikvic/webutility"
5 ) 9 )
6 10
7 func Headers(h http.HandlerFunc) http.HandlerFunc { 11 func Headers(h http.HandlerFunc) http.HandlerFunc {
8 return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(h))) 12 return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(h)))
9 } 13 }
10 14
11 func AuthUser(roles string, h http.HandlerFunc) http.HandlerFunc { 15 func AuthUser(roles string, h http.HandlerFunc) http.HandlerFunc {
12 return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(Auth(roles, h)))) 16 return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(Auth(roles, h))))
13 } 17 }
14 18
15 func AuthUserAndLog(roles string, h http.HandlerFunc) http.HandlerFunc { 19 func AuthUserAndLog(roles string, h http.HandlerFunc) http.HandlerFunc {
16 return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(LogHTTP(Auth(roles, h))))) 20 return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(LogHTTP(Auth(roles, h)))))
17 } 21 }
18 22
19 func LogTraffic(h http.HandlerFunc) http.HandlerFunc { 23 func LogTraffic(h http.HandlerFunc) http.HandlerFunc {
20 return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(LogHTTP(h)))) 24 return SetAccessControlHeaders(IgnoreOptionsRequests(ParseForm(LogHTTP(h))))
21 } 25 }
26
27 func TrafficLogsHandler(w http.ResponseWriter, req *http.Request) {
28 switch req.Method {
29 case "GET":
30 files, err := ioutil.ReadDir(httpLogger.GetOutDir() + "/")
31 if err != nil {
32 web.InternalServerError(w, req, err.Error())
33 return
34 }
35
36 web.SetContentType(w, "text/html; charset=utf-8")
37 web.SetResponseStatus(w, http.StatusOK)
38
39 web.WriteResponse(w, []byte("<body style='background-color: black; color: white'>"))
40 inputForm := `
41 <div>
42 <form action="/api/v1/logs" method="POST" target="_blank">
43 Username:<br>
44 <input type="text" name="username"><br>
45 Password:<br>
46 <input type="password" name="password"><br>
47 Log:<br>
48 <input type="text" name="logfile"><br>
49 <input type="submit" value="View">
50 </form>
51 </div>`
52 web.WriteResponse(w, []byte(inputForm))
53
54 web.WriteResponse(w, []byte("<table>"))
55 web.WriteResponse(w, []byte("<tr><th>Name</th><th>Size</th></tr>"))
56 for i := range files {
57 name := files[i].Name()
58 size := files[i].Size()
59 div := fmt.Sprintf(`<tr><td>%s</td><td style="text-align:right">%dB</td></tr>`, name, size)
60 web.WriteResponse(w, []byte(div))
61 }
62 web.WriteResponse(w, []byte("</table></body>"))
63
64 case "POST":
65 web.SetContentType(w, "text/html; charset=utf-8")
66
67 logfile := req.FormValue("logfile")
68 content, err := web.ReadFileContent(httpLogger.GetOutDir() + "/" + logfile)
69 if err != nil {
70 web.InternalServerError(w, req, err.Error())
71 return
72 }
73 web.SetResponseStatus(w, http.StatusOK)
74 web.WriteResponse(w, []byte("<body style='background-color: black; color: white'>"))
75 web.WriteResponse(w, []byte("<pre>"))
76 web.WriteResponse(w, content)
77 web.WriteResponse(w, []byte("</pre></body>"))
78 return
79 }
80 }
22 81
middleware/middleware.go
1 package middleware 1 package middleware
2 2
3 import ( 3 import (
4 "net/http" 4 "net/http"
5 "time" 5 "time"
6 6
7 "git.to-net.rs/marko.tikvic/gologger" 7 "git.to-net.rs/marko.tikvic/gologger"
8 8
9 web "git.to-net.rs/marko.tikvic/webutility" 9 web "git.to-net.rs/marko.tikvic/webutility"
10 ) 10 )
11 11
12 var httpLogger *gologger.Logger 12 var httpLogger *gologger.Logger
13 13
14 func SetAccessControlHeaders(h http.HandlerFunc) http.HandlerFunc { 14 func SetAccessControlHeaders(h http.HandlerFunc) http.HandlerFunc {
15 return func(w http.ResponseWriter, req *http.Request) { 15 return func(w http.ResponseWriter, req *http.Request) {
16 web.SetAccessControlHeaders(w) 16 web.SetAccessControlHeaders(w)
17 17
18 h(w, req) 18 h(w, req)
19 } 19 }
20 } 20 }
21 21
22 // IgnoreOptionsRequests ... 22 // IgnoreOptionsRequests ...
23 func IgnoreOptionsRequests(h http.HandlerFunc) http.HandlerFunc { 23 func IgnoreOptionsRequests(h http.HandlerFunc) http.HandlerFunc {
24 return func(w http.ResponseWriter, req *http.Request) { 24 return func(w http.ResponseWriter, req *http.Request) {
25 if req.Method == http.MethodOptions { 25 if req.Method == http.MethodOptions {
26 return 26 return
27 } 27 }
28 28
29 h(w, req) 29 h(w, req)
30 } 30 }
31 } 31 }
32 32
33 // ParseForm ... 33 // ParseForm ...
34 func ParseForm(h http.HandlerFunc) http.HandlerFunc { 34 func ParseForm(h http.HandlerFunc) http.HandlerFunc {
35 return func(w http.ResponseWriter, req *http.Request) { 35 return func(w http.ResponseWriter, req *http.Request) {
36 err := req.ParseForm() 36 err := req.ParseForm()
37 if err != nil { 37 if err != nil {
38 web.BadRequest(w, req, err.Error()) 38 web.BadRequest(w, req, err.Error())
39 return 39 return
40 } 40 }
41 41
42 h(w, req) 42 h(w, req)
43 } 43 }
44 } 44 }
45 45
46 // ParseMultipartForm ... 46 // ParseMultipartForm ...
47 func ParseMultipartForm(h http.HandlerFunc) http.HandlerFunc { 47 func ParseMultipartForm(h http.HandlerFunc) http.HandlerFunc {
48 return func(w http.ResponseWriter, req *http.Request) { 48 return func(w http.ResponseWriter, req *http.Request) {
49 err := req.ParseMultipartForm(32 << 20) 49 err := req.ParseMultipartForm(32 << 20)
50 if err != nil { 50 if err != nil {
51 web.BadRequest(w, req, err.Error()) 51 web.BadRequest(w, req, err.Error())
52 return 52 return
53 } 53 }
54 54
55 h(w, req) 55 h(w, req)
56 } 56 }
57 } 57 }
58 58
59 // SetLogger ... 59 // SetLogger ...
60 func SetLogger(logger *gologger.Logger) { 60 func SetLogger(logger *gologger.Logger) {
61 httpLogger = logger 61 httpLogger = logger
62 } 62 }
63 63
64 func StartLogging(filename, dir string) (err error) {
65 if httpLogger, err = gologger.New(filename, dir, gologger.MaxLogSize1MB); err != nil {
66 return err
67 }
68 return nil
69 }
70
71 func CloseLogger() {
72 httpLogger.Close()
73 }
74
64 // LogHTTP ... 75 // LogHTTP ...
65 func LogHTTP(h http.HandlerFunc) http.HandlerFunc { 76 func LogHTTP(h http.HandlerFunc) http.HandlerFunc {
66 return func(w http.ResponseWriter, req *http.Request) { 77 return func(w http.ResponseWriter, req *http.Request) {
67 if httpLogger == nil { 78 if httpLogger == nil {
68 h(w, req) 79 h(w, req)
69 return 80 return
70 } 81 }
71 82
72 t1 := time.Now() 83 t1 := time.Now()
73 84
74 claims, _ := web.GetTokenClaims(req) 85 claims, _ := web.GetTokenClaims(req)
75 in := httpLogger.LogHTTPRequest(req, claims.Username) 86 in := httpLogger.LogHTTPRequest(req, claims.Username)
76 87
77 rec := web.NewStatusRecorder(w) 88 rec := web.NewStatusRecorder(w)
78 89
79 h(rec, req) 90 h(rec, req)
80 91
81 t2 := time.Now() 92 t2 := time.Now()
82 out := httpLogger.LogHTTPResponse(rec.Status(), t2.Sub(t1), rec.Size()) 93 out := httpLogger.LogHTTPResponse(rec.Status(), t2.Sub(t1), rec.Size())
83 94
84 httpLogger.CombineHTTPLogs(in, out) 95 httpLogger.CombineHTTPLogs(in, out)
85 } 96 }
86 } 97 }
87 98
88 // Auth ... 99 // Auth ...
89 func Auth(roles string, h http.HandlerFunc) http.HandlerFunc { 100 func Auth(roles string, h http.HandlerFunc) http.HandlerFunc {
90 return func(w http.ResponseWriter, req *http.Request) { 101 return func(w http.ResponseWriter, req *http.Request) {
91 if _, err := web.AuthCheck(req, roles); err != nil { 102 if _, err := web.AuthCheck(req, roles); err != nil {
92 web.Unauthorized(w, req, err.Error()) 103 web.Unauthorized(w, req, err.Error())
93 return 104 return
94 } 105 }
95 106
96 h(w, req) 107 h(w, req)
97 } 108 }
98 } 109 }
99 110
File was created 1 package webutility
2
3 import (
4 "database/sql"
5 "fmt"
6 "net/http"
7
8 "git.to-net.rs/marko.tikvic/gologger"
9 "github.com/gorilla/mux"
10 )
11
12 type Server struct {
13 DB *sql.DB
14 Router *mux.Router
15 Logger *gologger.Logger
16 Port string
17 UTCOffset int64
18 }
19
20 func NewServer(dsn, port, logDir string, utcOffset int64) (s *Server, err error) {
21 s = new(Server)
22
23 s.Port = port
24
25 if s.DB, err = sql.Open("odbc", fmt.Sprintf("DSN=%s;", dsn)); err != nil {
26 return nil, err
27 }
28
29 s.Router = mux.NewRouter()
30
31 if s.Logger, err = gologger.New("err", logDir, gologger.MaxLogSize1MB); err != nil {
32 return nil, fmt.Errorf("can't create logger: %s", err.Error())
33 }
34
35 s.UTCOffset = utcOffset
36
37 return s, nil
38 }
39
40 func (s *Server) Run() {
41 s.Logger.Print("Server listening on %s", s.Port)
42 s.Logger.PrintAndTrace(http.ListenAndServe(s.Port, s.Router).Error())
43 }
44
45 func (s *Server) Cleanup() {
46 if s.DB != nil {
47 s.DB.Close()
48 }
49
50 if s.Logger != nil {
51 s.Logger.Close()
52 }
53 }
54
55 func (s *Server) StartTransaction() (*sql.Tx, error) {
56 return s.DB.Begin()
57 }
58
59 func CommitChanges(tx *sql.Tx, err *error, opt ...error) {
60 if *err != nil {
61 tx.Rollback()
62 return
63 }
64
65 for _, e := range opt {
66 if e != nil {
67 tx.Rollback()
68 return
69 }
70 }
71
72 if *err = tx.Commit(); *err != nil {
73 tx.Rollback()
74 }
75 }
76
1 package webutility 1 package webutility
2 2
3 import ( 3 import (
4 "fmt" 4 "fmt"
5 "strconv" 5 "strconv"
6 "strings" 6 "strings"
7 "unicode" 7 "unicode"
8 ) 8 )
9 9
10 const sanitisationPatern = "\"';&*<>=\\`:" 10 const sanitisationPatern = "\"';&*<>=\\`:"
11 11
12 // SanitiseString removes characters from s found in patern and returns new modified string. 12 // SanitiseString removes characters from s found in patern and returns new modified string.
13 func SanitiseString(s string) string { 13 func SanitiseString(s string) string {
14 return ReplaceAny(s, sanitisationPatern, "") 14 return ReplaceAny(s, sanitisationPatern, "")
15 } 15 }
16 16
17 // IsWrappedWith ... 17 // IsWrappedWith ...
18 func IsWrappedWith(src, begin, end string) bool { 18 func IsWrappedWith(src, begin, end string) bool {
19 return strings.HasPrefix(src, begin) && strings.HasSuffix(src, end) 19 return strings.HasPrefix(src, begin) && strings.HasSuffix(src, end)
20 } 20 }
21 21
22 // ParseInt64Arr ... 22 // ParseInt64Arr ...
23 func ParseInt64Arr(s, sep string) (arr []int64) { 23 func ParseInt64Arr(s, sep string) (arr []int64) {
24 s = strings.TrimSpace(s) 24 s = strings.TrimSpace(s)
25 if s != "" { 25 if s != "" {
26 parts := strings.Split(s, sep) 26 parts := strings.Split(s, sep)
27 arr = make([]int64, len(parts)) 27 arr = make([]int64, len(parts))
28 for i, p := range parts { 28 for i, p := range parts {
29 num := StringToInt64(p) 29 num := StringToInt64(p)
30 arr[i] = num 30 arr[i] = num
31 } 31 }
32 } 32 }
33 33
34 return arr 34 return arr
35 } 35 }
36 36
37 // Int64SliceToString ... 37 // Int64SliceToString ...
38 func Int64SliceToString(arr []int64) (s string) { 38 func Int64SliceToString(arr []int64) (s string) {
39 if len(arr) == 0 { 39 if len(arr) == 0 {
40 return "" 40 return ""
41 } 41 }
42 42
43 s += fmt.Sprintf("%d", arr[0]) 43 s += fmt.Sprintf("%d", arr[0])
44 for i := 1; i < len(arr); i++ { 44 for i := 1; i < len(arr); i++ {
45 s += fmt.Sprintf(",%d", arr[i]) 45 s += fmt.Sprintf(",%d", arr[i])
46 } 46 }
47 47
48 return s 48 return s
49 } 49 }
50 50
51 // CombineStrings ... 51 // CombineStrings ...
52 func CombineStrings(s1, s2, s3 string) string { 52 func CombineStrings(s1, s2, s3 string) string {
53 s1 = strings.TrimSpace(s1) 53 s1 = strings.TrimSpace(s1)
54 s2 = strings.TrimSpace(s2) 54 s2 = strings.TrimSpace(s2)
55 55
56 if s1 != "" && s2 != "" { 56 if s1 != "" && s2 != "" {
57 s1 += s3 + s2 57 s1 += s3 + s2
58 } else { 58 } else {
59 s1 += s2 59 s1 += s2
60 } 60 }
61 61
62 return s1 62 return s1
63 } 63 }
64 64
65 // ReplaceAny replaces any of the characters from patern found in s with r and returns a new resulting string. 65 // ReplaceAny replaces any of the characters from patern found in s with r and returns a new resulting string.
66 func ReplaceAny(s, patern, r string) (n string) { 66 func ReplaceAny(s, patern, r string) (n string) {
67 n = s 67 n = s
68 for _, c := range patern { 68 for _, c := range patern {
69 n = strings.Replace(n, string(c), r, -1) 69 n = strings.Replace(n, string(c), r, -1)
70 } 70 }
71 return n 71 return n
72 } 72 }
73 73
74 // StringToBool ... 74 // StringToBool ...
75 func StringToBool(s string) bool { 75 func StringToBool(s string) bool {
76 res, _ := strconv.ParseBool(s) 76 res, _ := strconv.ParseBool(s)
77 return res 77 return res
78 } 78 }
79 79
80 // BoolToString ... 80 // BoolToString ...
81 func BoolToString(b bool) string { 81 func BoolToString(b bool) string {
82 return fmt.Sprintf("%b", b) 82 return fmt.Sprintf("%b", b)
83 } 83 }
84 84
85 // StringSliceContains ... 85 // StringSliceContains ...
86 func StringSliceContains(slice []string, s string) bool { 86 func StringSliceContains(slice []string, s string) bool {
87 for i := range slice { 87 for i := range slice {
88 if slice[i] == s { 88 if slice[i] == s {
89 return true 89 return true
90 } 90 }
91 } 91 }
92 return false 92 return false
93 } 93 }
94 94
95 func SplitString(s, sep string) (res []string) { 95 func SplitString(s, sep string) (res []string) {
96 parts := strings.Split(s, sep) 96 parts := strings.Split(s, sep)
97 for _, p := range parts { 97 for _, p := range parts {
98 if p != "" { 98 if p != "" {
99 res = append(res, p) 99 res = append(res, p)
100 } 100 }
101 } 101 }
102 return res 102 return res
103 } 103 }
104 104
105 // StringAt ... 105 // StringAt ...
106 func StringAt(s string, index int) string { 106 func StringAt(s string, index int) string {
107 if len(s)-1 < index || index < 0 { 107 if len(s)-1 < index || index < 0 {
108 return "" 108 return ""
109 } 109 }
110 110
111 return string(s[index]) 111 return string(s[index])
112 } 112 }
113 113
114 // SplitText ... 114 // SplitText ...
115 func SplitText(s string, maxLen int) (lines []string) { 115 func SplitText(s string, maxLen int) (lines []string) {
116 runes := []rune(s) 116 runes := []rune(s)
117 117
118 i, start, sep, l := 0, 0, 0, 0 118 i, start, sep, l := 0, 0, 0, 0
119 for i = 0; i < len(runes); i++ { 119 for i = 0; i < len(runes); i++ {
120 c := runes[i] 120 c := runes[i]
121 121
122 if unicode.IsSpace(c) { 122 if unicode.IsSpace(c) {
123 sep = i 123 sep = i
124 } 124 }
125 125
126 if c == '\n' { 126 if c == '\n' {
127 if start != sep { 127 if start != sep {
128 lines = append(lines, string(runes[start:sep])) 128 lines = append(lines, string(runes[start:sep]))
129 } 129 }
130 start = i 130 start = i
131 sep = i 131 sep = i
132 l = 0 132 l = 0
133 } else if l >= maxLen { 133 } else if l >= maxLen {
134 if start != sep { 134 if start != sep {
135 lines = append(lines, string(runes[start:sep])) 135 lines = append(lines, string(runes[start:sep]))
136 sep = i 136 sep = i
137 start = i - 1 137 start = i - 1
138 l = 0 138 l = 0
139 } 139 }
140 } else { 140 } else {
141 l++ 141 l++
142 } 142 }
143 } 143 }
144 if start != i-1 { 144 if start != i-1 {
145 lines = append(lines, string(runes[start:i-1])) 145 lines = append(lines, string(runes[start:i-1]))
146 } 146 }
147 147
148 return lines 148 return lines
149 } 149 }
150 150
151 func CutTextWithThreeDots(txt string, maxLen int) string {
152 if len(txt) < maxLen || len(txt) <= 3 {
153 return txt
154 }
155
156 return txt[:maxLen-3] + "..."
157 }
158
151 const threeDots = "\u2056\u2056\u2056" 159 const threeDots = "\u2056\u2056\u2056"
152 160
153 func LimitTextWithThreeDots(txt string, maxLen int) string { 161 func LimitTextWithThreeDots(txt string, maxLen int) string {
154 if len(txt) <= maxLen { 162 if len(txt) <= maxLen {
155 return txt 163 return txt
156 } 164 }
157 165
158 return txt[:maxLen] + threeDots 166 return txt[:maxLen] + threeDots
159 } 167 }
160 168
169 func LimitMSWordTextWithThreeDots(txt string, maxLen int) string {
170 if len(txt) <= maxLen {
171 return txt
172 }
173
174 return txt[:maxLen] + "..."
175 }
176
161 // SplitStringAtWholeWords ... 177 // SplitStringAtWholeWords ...
162 func SplitStringAtWholeWords(s string, maxLen int) (res []string) { 178 func SplitStringAtWholeWords(s string, maxLen int) (res []string) {
163 parts := strings.Split(s, " ") 179 parts := strings.Split(s, " ")
164 180
165 res = append(res, parts[0]) 181 res = append(res, parts[0])
166 i := 0 182 i := 0
167 for j := 1; j < len(parts); j++ { 183 for j := 1; j < len(parts); j++ {
168 p := strings.TrimSpace(parts[j]) 184 p := strings.TrimSpace(parts[j])
169 if len(p) > maxLen { 185 if len(p) > maxLen {
170 // TODO(marko): check if maxLen is >= 3 186 // TODO(marko): check if maxLen is >= 3
171 p = p[0 : maxLen-3] 187 p = p[0 : maxLen-3]
172 p += "..." 188 p += "..."
173 } 189 }
174 if len(res[i])+len(p)+1 <= maxLen { 190 if len(res[i])+len(p)+1 <= maxLen {
175 res[i] += " " + p 191 res[i] += " " + p
176 } else { 192 } else {
177 res = append(res, p) 193 res = append(res, p)
178 i++ 194 i++
179 } 195 }
180 } 196 }
181 197
182 return res 198 return res
183 } 199 }
184 200
File was created 1 package webutility
2
3 import (
4 "fmt"
5 "time"
6 )
7
8 // Timer ...
9 type Timer struct {
10 name string
11 running bool
12 started time.Time
13 stopped time.Time
14 lastDuration time.Duration
15 }
16
17 // NewTimer ...
18 func NewTimer(name string) *Timer {
19 t := &Timer{name: name}
20 t.Reset()
21 return t
22 }
23
24 // Start ...
25 func (t *Timer) Start() time.Time {
26 t.running = true
27 t.started = time.Now()
28 return t.started
29 }
30
31 // Stop ...
32 func (t *Timer) Stop() time.Duration {
33 t.running = false
34 t.stopped = time.Now()
35 t.lastDuration = t.stopped.Sub(t.started)
36 return t.lastDuration
37 }
38
39 // LastRunDuration ...
40 func (t *Timer) LastRunDuration() time.Duration {
41 return t.lastDuration
42 }
43
44 // Clear ...
45 func (t *Timer) Clear() {
46 t.started = time.Now()
47 t.stopped = time.Now()
48 t.lastDuration = 0
49 }
50
51 // Reset ...
52 func (t *Timer) Reset() {
53 t.Stop()
54 t.Start()
55 }
56
57 // Elapsed ...
58 func (t *Timer) Elapsed() time.Duration {
59 if t.running {
60 return time.Now().Sub(t.started)
61 }
62 return 0
63 }
64
65 // Print ...
66 func (t *Timer) Print(s string) {
67 status := "RUNNING"
68 if !t.running {
69 status = "STOPPED"
70 }
71 fmt.Printf("timer[%s][%s]: %v %s\n", t.name, status, t.Elapsed(), s)
72 }
73
74 // Lap ...
75 func (t *Timer) Lap(s string) {
76 t.Print(s)
77 t.Reset()
78 }
79