-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathresponse.go
144 lines (124 loc) · 3.51 KB
/
response.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
package http
import (
"fmt"
"net"
"sort"
"time"
)
// Status codes
const (
StatusOK = 200
StatusCreated = 201
StatusBadRequest = 400
StatusNotFound = 404
StatusRequestTimeout = 408
StatusServerError = 500
StatusGatewayTimeout = 504
)
// StatusDescription returns a status description for the given status code
func StatusDescription(code int) string {
switch code {
case 200:
return "OK"
case 201:
return "Created"
case 400:
return "Bad Request"
case 404:
return "Not Found"
case 408:
return "Request Timeout"
case 500:
return "Internal Server Error"
case 504:
return "Gateway Timeout"
default:
return ""
}
}
// ResponseWriter provides methods to construct and send the HTTP response
type ResponseWriter interface {
// Write writes the byte slice to the response body
Write([]byte) (int, error)
// SetStatus sets the HTTP status code for the response
SetStatus(statusCode int)
// SetHeader sets a key-value pair in the HTTP response headers
SetHeader(key, value string)
}
// DefaultResponseWriter implements ResponseWriter interface
type DefaultResponseWriter struct {
conn net.Conn
statusCode int
headers map[string]string
wroteStatus bool
writeTimeout time.Duration
}
// NewResponseWriter returns new response writer
func NewResponseWriter(conn net.Conn, writeTimeout time.Duration) *DefaultResponseWriter {
return &DefaultResponseWriter{
conn: conn,
headers: make(map[string]string),
writeTimeout: writeTimeout,
}
}
// SetStatus writes status code of response
func (rw *DefaultResponseWriter) SetStatus(statusCode int) {
if rw.wroteStatus {
return
}
rw.statusCode = statusCode
rw.wroteStatus = true
}
// SetHeader sets headers
func (rw *DefaultResponseWriter) SetHeader(key, value string) {
rw.headers[key] = value
}
// Write sends resonse to client
func (rw *DefaultResponseWriter) Write(body []byte) (int, error) {
// Set default status
if !rw.wroteStatus {
rw.SetStatus(200)
}
// Set default content type
if _, exists := rw.headers["Content-Type"]; !exists {
rw.SetHeader("Content-Type", "text/plain")
}
// Set Content-Length
if _, exists := rw.headers["Content-Length"]; !exists {
rw.SetHeader("Content-Length", fmt.Sprintf("%d", len(body)))
}
// Set write deadlines
rw.conn.SetWriteDeadline(time.Now().Add(rw.writeTimeout))
defer rw.conn.SetWriteDeadline(time.Time{})
// Write status
responseLine := fmt.Sprintf("HTTP/1.1 %d %s\r\n", rw.statusCode, StatusDescription(rw.statusCode))
if _, err := rw.conn.Write([]byte(responseLine)); err != nil {
return 0, fmt.Errorf("failed to write response line: %w", err)
}
// Get the header keys and sort them
keys := make([]string, 0, len(rw.headers))
for key := range rw.headers {
keys = append(keys, key)
}
sort.Strings(keys)
rw.conn.SetWriteDeadline(time.Now().Add(rw.writeTimeout))
// Write headers in sorted order
for _, key := range keys {
headerLine := fmt.Sprintf("%s: %s\r\n", key, rw.headers[key])
if _, err := rw.conn.Write([]byte(headerLine)); err != nil {
return 0, fmt.Errorf("failed to write header: %w", err)
}
}
rw.conn.SetWriteDeadline(time.Now().Add(rw.writeTimeout))
// Write \r\n between headers and body
if _, err := rw.conn.Write([]byte("\r\n")); err != nil {
return 0, fmt.Errorf("failed to write blank line after headers: %w", err)
}
rw.conn.SetWriteDeadline(time.Now().Add(rw.writeTimeout))
// Write body
n, err := rw.conn.Write(body)
if err != nil {
return n, fmt.Errorf("failed to write body: %w", err)
}
return n, nil
}