parseResponse -> "post".. it doesn't just parse.. it actually dial out to
do the request. parseResponseMultipart => postWithMultipartResponse too.
Got rid of the "Config"... we don't need "protocol" and "origin" for the
websocket on Slack, no need to keep that around. So only "token" was left.
Simplified parseResponseBody.. was slightly convoluted.
Attached the "search" to the Slack methods. This way, we don,t need to pass
the token or the debug flag. Will do the same with "post()" next.
Moved things around in websocket.go websocket_*.go
Stubbed "manageConnection" connect/disconnect manager.
Alexandre Bourget authored 8 years ago
Norberto Lopes committed 8 years ago
25 | 25 | |
26 | 26 | func channelRequest(path string, values url.Values, debug bool) (*channelResponseFull, error) { |
27 | 27 | response := &channelResponseFull{} |
28 | err := parseResponse(path, values, response, debug) | |
28 | err := post(path, values, response, debug) | |
29 | 29 | if err != nil { |
30 | 30 | return nil, err |
31 | 31 | } |
61 | 61 | |
62 | 62 | func chatRequest(path string, values url.Values, debug bool) (*chatResponseFull, error) { |
63 | 63 | response := &chatResponseFull{} |
64 | err := parseResponse(path, values, response, debug) | |
64 | err := post(path, values, response, debug) | |
65 | 65 | if err != nil { |
66 | 66 | return nil, err |
67 | 67 | } |
92 | 92 | |
93 | 93 | // PostMessage sends a message to a channel. |
94 | 94 | // Message is escaped by default according to https://api.slack.com/docs/formatting |
95 | // Use http://davestevens.github.io/slack-message-builder/ to help crafting your message. | |
95 | 96 | func (api *Slack) PostMessage(channel string, text string, params PostMessageParameters) (string, string, error) { |
96 | 97 | if params.EscapeText { |
97 | 98 | text = escapeMessage(text) |
0 | package slack | |
1 | ||
2 | // Config contains some config parameters needed | |
3 | // Token always needs to be set for the api to function | |
4 | // Origin and Protocol are optional and only needed for websocket | |
5 | type Config struct { | |
6 | token string | |
7 | origin string | |
8 | protocol string | |
9 | } |
15 | 15 | "token": {api.config.token}, |
16 | 16 | } |
17 | 17 | response := &emojiResponseFull{} |
18 | err := parseResponse("emoji.list", values, response, api.debug) | |
18 | err := post("emoji.list", values, response, api.debug) | |
19 | 19 | if err != nil { |
20 | 20 | return nil, err |
21 | 21 | } |
108 | 108 | |
109 | 109 | func fileRequest(path string, values url.Values, debug bool) (*fileResponseFull, error) { |
110 | 110 | response := &fileResponseFull{} |
111 | err := parseResponse(path, values, response, debug) | |
111 | err := post(path, values, response, debug) | |
112 | 112 | if err != nil { |
113 | 113 | return nil, err |
114 | 114 | } |
193 | 193 | } |
194 | 194 | if params.Content != "" { |
195 | 195 | values.Add("content", params.Content) |
196 | err = parseResponse("files.upload", values, response, api.debug) | |
196 | err = post("files.upload", values, response, api.debug) | |
197 | 197 | } else if params.File != "" { |
198 | err = parseResponseMultipart("files.upload", params.File, values, response, api.debug) | |
198 | err = postWithMultipartResponse("files.upload", params.File, values, response, api.debug) | |
199 | 199 | } |
200 | 200 | if err != nil { |
201 | 201 | return nil, err |
28 | 28 | |
29 | 29 | func groupRequest(path string, values url.Values, debug bool) (*groupResponseFull, error) { |
30 | 30 | response := &groupResponseFull{} |
31 | err := parseResponse(path, values, response, debug) | |
31 | err := post(path, values, response, debug) | |
32 | 32 | if err != nil { |
33 | 33 | return nil, err |
34 | 34 | } |
29 | 29 | |
30 | 30 | func imRequest(path string, values url.Values, debug bool) (*imResponseFull, error) { |
31 | 31 | response := &imResponseFull{} |
32 | err := parseResponse(path, values, response, debug) | |
32 | err := post(path, values, response, debug) | |
33 | 33 | if err != nil { |
34 | 34 | return nil, err |
35 | 35 | } |
58 | 58 | } |
59 | 59 | |
60 | 60 | func parseResponseBody(body io.ReadCloser, intf *interface{}, debug bool) error { |
61 | var decoder *json.Decoder | |
62 | if debug { | |
63 | response, err := ioutil.ReadAll(body) | |
64 | if err != nil { | |
65 | return err | |
66 | } | |
67 | log.Println(string(response)) | |
68 | decoder = json.NewDecoder(bytes.NewReader(response)) | |
69 | } else { | |
70 | decoder = json.NewDecoder(body) | |
71 | } | |
72 | if err := decoder.Decode(&intf); err != nil { | |
61 | response, err := ioutil.ReadAll(body) | |
62 | if err != nil { | |
73 | 63 | return err |
74 | 64 | } |
65 | ||
66 | if debug { | |
67 | log.Printf("parseResponseBody: %s\n", string(response)) | |
68 | } | |
69 | ||
70 | err = json.Unmarshal(response, &intf) | |
71 | if err != nil { | |
72 | return err | |
73 | } | |
74 | ||
75 | 75 | return nil |
76 | ||
77 | 76 | } |
78 | 77 | |
79 | func parseResponseMultipart(path string, filepath string, values url.Values, intf interface{}, debug bool) error { | |
78 | func postWithMultipartResponse(path string, filepath string, values url.Values, intf interface{}, debug bool) error { | |
80 | 79 | req, err := fileUploadReq(SLACK_API+path, filepath, values) |
81 | 80 | client := &http.Client{} |
82 | 81 | resp, err := client.Do(req) |
96 | 95 | return parseResponseBody(resp.Body, &intf, debug) |
97 | 96 | } |
98 | 97 | |
99 | func parseResponse(path string, values url.Values, intf interface{}, debug bool) error { | |
98 | func post(path string, values url.Values, intf interface{}, debug bool) error { | |
100 | 99 | return postForm(SLACK_API+path, values, intf, debug) |
101 | 100 | } |
102 | 101 |
39 | 39 | "token": {validToken}, |
40 | 40 | } |
41 | 41 | responsePartial := &SlackResponse{} |
42 | err := parseResponse("parseResponse", values, responsePartial, false) | |
42 | err := post("parseResponse", values, responsePartial, false) | |
43 | 43 | if err != nil { |
44 | 44 | t.Errorf("Unexpected error: %s", err) |
45 | 45 | } |
51 | 51 | SLACK_API = "http://" + serverAddr + "/" |
52 | 52 | values := url.Values{} |
53 | 53 | responsePartial := &SlackResponse{} |
54 | err := parseResponse("parseResponse", values, responsePartial, false) | |
54 | err := post("parseResponse", values, responsePartial, false) | |
55 | 55 | if err != nil { |
56 | 56 | t.Errorf("Unexpected error: %s", err) |
57 | 57 | return |
71 | 71 | "token": {"whatever"}, |
72 | 72 | } |
73 | 73 | responsePartial := &SlackResponse{} |
74 | err := parseResponse("parseResponse", values, responsePartial, false) | |
74 | err := post("parseResponse", values, responsePartial, false) | |
75 | 75 | if err != nil { |
76 | 76 | t.Errorf("Unexpected error: %s", err) |
77 | 77 | return |
19 | 19 | "redirect_uri": {redirectURI}, |
20 | 20 | } |
21 | 21 | response := &oAuthResponseFull{} |
22 | err = parseResponse("oauth.access", values, response, debug) | |
22 | err = post("oauth.access", values, response, debug) | |
23 | 23 | if err != nil { |
24 | 24 | return "", "", err |
25 | 25 | } |
79 | 79 | } |
80 | 80 | } |
81 | 81 | |
82 | func search(token, path, query string, params SearchParameters, files, messages, debug bool) (response *searchResponseFull, error error) { | |
82 | func (api *Slack) _search(path, query string, params SearchParameters, files, messages bool) (response *searchResponseFull, error error) { | |
83 | 83 | values := url.Values{ |
84 | "token": {token}, | |
84 | "token": {api.config.token}, | |
85 | 85 | "query": {query}, |
86 | 86 | } |
87 | 87 | if params.Sort != DEFAULT_SEARCH_SORT { |
100 | 100 | values.Add("page", strconv.Itoa(params.Page)) |
101 | 101 | } |
102 | 102 | response = &searchResponseFull{} |
103 | err := parseResponse(path, values, response, debug) | |
103 | err := post(path, values, response, api.debug) | |
104 | 104 | if err != nil { |
105 | 105 | return nil, err |
106 | 106 | } |
112 | 112 | } |
113 | 113 | |
114 | 114 | func (api *Slack) Search(query string, params SearchParameters) (*SearchMessages, *SearchFiles, error) { |
115 | response, err := search(api.config.token, "search.all", query, params, true, true, api.debug) | |
115 | response, err := api._search("search.all", query, params, true, true) | |
116 | 116 | if err != nil { |
117 | 117 | return nil, nil, err |
118 | 118 | } |
120 | 120 | } |
121 | 121 | |
122 | 122 | func (api *Slack) SearchFiles(query string, params SearchParameters) (*SearchFiles, error) { |
123 | response, err := search(api.config.token, "search.files", query, params, true, false, api.debug) | |
123 | response, err := api._search("search.files", query, params, true, false) | |
124 | 124 | if err != nil { |
125 | 125 | return nil, err |
126 | 126 | } |
128 | 128 | } |
129 | 129 | |
130 | 130 | func (api *Slack) SearchMessages(query string, params SearchParameters) (*SearchMessages, error) { |
131 | response, err := search(api.config.token, "search.messages", query, params, false, true, api.debug) | |
131 | response, err := api._search("search.messages", query, params, false, true) | |
132 | 132 | if err != nil { |
133 | 133 | return nil, err |
134 | 134 | } |
29 | 29 | } |
30 | 30 | |
31 | 31 | type Slack struct { |
32 | config Config | |
33 | info Info | |
34 | debug bool | |
32 | config struct { | |
33 | token string | |
34 | } | |
35 | info Info | |
36 | debug bool | |
35 | 37 | } |
36 | 38 | |
37 | 39 | func New(token string) *Slack { |
38 | return &Slack{ | |
39 | config: Config{token: token}, | |
40 | } | |
40 | s := &Slack{} | |
41 | s.config.token = token | |
42 | return s | |
41 | 43 | } |
42 | 44 | |
43 | 45 | func (api *Slack) GetInfo() Info { |
47 | 49 | // AuthTest tests if the user is able to do authenticated requests or not |
48 | 50 | func (api *Slack) AuthTest() (response *AuthTestResponse, error error) { |
49 | 51 | responseFull := &authTestResponseFull{} |
50 | err := parseResponse("auth.test", url.Values{"token": {api.config.token}}, responseFull, api.debug) | |
52 | err := post("auth.test", url.Values{"token": {api.config.token}}, responseFull, api.debug) | |
51 | 53 | if err != nil { |
52 | 54 | return nil, err |
53 | 55 | } |
60 | 60 | values.Add("page", strconv.Itoa(params.Page)) |
61 | 61 | } |
62 | 62 | response := &starsResponseFull{} |
63 | err := parseResponse("stars.list", values, response, api.debug) | |
63 | err := post("stars.list", values, response, api.debug) | |
64 | 64 | if err != nil { |
65 | 65 | return nil, nil, err |
66 | 66 | } |
63 | 63 | |
64 | 64 | func userRequest(path string, values url.Values, debug bool) (*userResponseFull, error) { |
65 | 65 | response := &userResponseFull{} |
66 | err := parseResponse(path, values, response, debug) | |
66 | err := post(path, values, response, debug) | |
67 | 67 | if err != nil { |
68 | 68 | return nil, err |
69 | 69 | } |
4 | 4 | "fmt" |
5 | 5 | "io" |
6 | 6 | "log" |
7 | "net" | |
8 | 7 | "net/url" |
9 | 8 | "reflect" |
10 | "strconv" | |
11 | 9 | "sync" |
12 | 10 | "time" |
13 | 11 | |
14 | 12 | "golang.org/x/net/websocket" |
15 | 13 | ) |
16 | 14 | |
17 | // MessageEvent represents the Message event | |
18 | type MessageEvent Message | |
19 | ||
20 | // WS contains websocket metadata | |
21 | type WS struct { | |
22 | conn *websocket.Conn | |
23 | messageID int | |
15 | // SlackWS represents a managed websocket connection. It also supports all the methods of the `Slack` type. | |
16 | type SlackWS struct { | |
24 | 17 | mutex sync.Mutex |
18 | messageId int | |
25 | 19 | pings map[int]time.Time |
20 | ||
21 | // Connection life-cycle | |
22 | conn *websocket.Conn | |
23 | IncomingEvents chan SlackEvent | |
24 | connectionErrors chan error | |
25 | killRoutines chan bool | |
26 | ||
27 | // Slack is the main API, embedded | |
26 | 28 | Slack |
27 | 29 | } |
28 | 30 | |
29 | // SlackEvent is the main wrapper. You will find all the other messages attached | |
30 | type SlackEvent struct { | |
31 | Type string | |
32 | Data interface{} | |
33 | } | |
34 | ||
35 | // AckMessage is used for messages received in reply to other messages | |
36 | type AckMessage struct { | |
37 | ReplyTo int `json:"reply_to"` | |
38 | Timestamp string `json:"ts"` | |
39 | Text string `json:"text"` | |
40 | WSResponse | |
41 | } | |
42 | ||
43 | // WSResponse is a websocket response | |
44 | type WSResponse struct { | |
45 | Ok bool `json:"ok"` | |
46 | Error *WSError `json:"error"` | |
47 | } | |
48 | ||
49 | // WSError is a websocket error | |
50 | type WSError struct { | |
51 | Code int | |
52 | Msg string | |
53 | } | |
54 | ||
55 | // JSONTimeString is a JSON unix timestamp | |
56 | type JSONTimeString string | |
57 | ||
58 | // String converts the unix timestamp into a string | |
59 | func (t JSONTimeString) String() string { | |
60 | if t == "" { | |
61 | return "" | |
62 | } | |
63 | floatN, err := strconv.ParseFloat(string(t), 64) | |
64 | if err != nil { | |
65 | log.Panicln(err) | |
66 | return "" | |
67 | } | |
68 | timeStr := int64(floatN) | |
69 | tm := time.Unix(int64(timeStr), 0) | |
70 | return fmt.Sprintf("\"%s\"", tm.Format("Mon Jan _2")) | |
71 | } | |
72 | ||
73 | func (s WSError) Error() string { | |
74 | return s.Msg | |
75 | } | |
76 | ||
77 | var portMapping = map[string]string{"ws": "80", "wss": "443"} | |
78 | ||
79 | func fixURLPort(orig string) (string, error) { | |
80 | urlObj, err := url.ParseRequestURI(orig) | |
81 | if err != nil { | |
82 | return "", err | |
83 | } | |
84 | _, _, err = net.SplitHostPort(urlObj.Host) | |
85 | if err != nil { | |
86 | return urlObj.Scheme + "://" + urlObj.Host + ":" + portMapping[urlObj.Scheme] + urlObj.Path, nil | |
87 | } | |
88 | return orig, nil | |
89 | } | |
90 | ||
91 | func (api *Slack) StartRTM(protocol, origin string) (*WS, error) { | |
31 | // StartRTM starts a Websocket used to do all common chat client operations. | |
32 | func (api *Slack) StartRTM() (*SlackWS, error) { | |
92 | 33 | response := &infoResponseFull{} |
93 | err := parseResponse("rtm.start", url.Values{"token": {api.config.token}}, response, api.debug) | |
34 | err := post("rtm.start", url.Values{"token": {api.config.token}}, response, api.debug) | |
94 | 35 | if err != nil { |
95 | 36 | return nil, err |
96 | 37 | } |
101 | 42 | // websocket.Dial does not accept url without the port (yet) |
102 | 43 | // Fixed by: https://github.com/golang/net/commit/5058c78c3627b31e484a81463acd51c7cecc06f3 |
103 | 44 | // but slack returns the address with no port, so we have to fix it |
104 | api.info.URL, err = fixURLPort(api.info.URL) | |
45 | websocketUrl, err := websocketizeUrlPort(api.info.Url) | |
105 | 46 | if err != nil { |
106 | 47 | return nil, err |
107 | 48 | } |
108 | api.config.protocol, api.config.origin = protocol, origin | |
109 | wsAPI := &WS{Slack: *api} | |
110 | wsAPI.conn, err = websocket.Dial(api.info.URL, api.config.protocol, api.config.origin) | |
49 | ws := &SlackWS{Slack: *api} | |
50 | ws.pings = make(map[int]time.Time) | |
51 | ws.conn, err = websocket.Dial(websocketUrl, "", "") | |
111 | 52 | if err != nil { |
112 | 53 | return nil, err |
113 | 54 | } |
114 | wsAPI.pings = make(map[int]time.Time) | |
115 | return wsAPI, nil | |
116 | } | |
117 | ||
118 | func (api *WS) Ping() error { | |
119 | api.mutex.Lock() | |
120 | defer api.mutex.Unlock() | |
121 | api.messageID++ | |
122 | msg := &Ping{ID: api.messageID, Type: "ping"} | |
123 | if err := websocket.JSON.Send(api.conn, msg); err != nil { | |
55 | ||
56 | ws.IncomingEvents = make(chan SlackEvent, 50) | |
57 | ws.killRoutines = make(chan bool, 10) | |
58 | ws.connectionErrors = make(chan error, 10) | |
59 | go ws.manageConnection(websocketUrl) | |
60 | return ws, nil | |
61 | } | |
62 | ||
63 | func (ws *SlackWS) manageConnection(url string) { | |
64 | // receive any connectionErrors, killall goroutines | |
65 | // reconnect and restart them all | |
66 | } | |
67 | ||
68 | func (ws *SlackWS) Ping() error { | |
69 | ws.mutex.Lock() | |
70 | defer ws.mutex.Unlock() | |
71 | ws.messageId++ | |
72 | msg := &Ping{Id: ws.messageId, Type: "ping"} | |
73 | if err := websocket.JSON.Send(ws.conn, msg); err != nil { | |
124 | 74 | return err |
125 | 75 | } |
126 | 76 | // TODO: What happens if we already have this id? |
127 | api.pings[api.messageID] = time.Now() | |
77 | ws.pings[ws.messageId] = time.Now() | |
128 | 78 | return nil |
129 | 79 | } |
130 | 80 | |
131 | func (api *WS) Keepalive(interval time.Duration) { | |
81 | func (ws *SlackWS) Keepalive(interval time.Duration) { | |
132 | 82 | ticker := time.NewTicker(interval) |
133 | 83 | defer ticker.Stop() |
134 | 84 | |
135 | 85 | for { |
136 | 86 | select { |
137 | 87 | case <-ticker.C: |
138 | if err := api.Ping(); err != nil { | |
88 | if err := ws.Ping(); err != nil { | |
139 | 89 | log.Fatal(err) |
140 | 90 | } |
141 | 91 | } |
142 | 92 | } |
143 | 93 | } |
144 | 94 | |
145 | func (api *WS) SendMessage(msg *OutgoingMessage) error { | |
146 | api.mutex.Lock() | |
147 | defer api.mutex.Unlock() | |
95 | func (ws *SlackWS) SendMessage(msg *OutgoingMessage) error { | |
96 | ws.mutex.Lock() | |
97 | defer ws.mutex.Unlock() | |
148 | 98 | |
149 | 99 | if msg == nil { |
150 | 100 | return fmt.Errorf("Can't send a nil message") |
151 | 101 | } |
152 | 102 | |
153 | if err := websocket.JSON.Send(api.conn, *msg); err != nil { | |
103 | if err := websocket.JSON.Send(ws.conn, *msg); err != nil { | |
154 | 104 | return err |
155 | 105 | } |
156 | 106 | return nil |
157 | 107 | } |
158 | 108 | |
159 | func (api *WS) HandleIncomingEvents(ch chan SlackEvent) { | |
109 | func (ws *SlackWS) HandleIncomingEvents(ch chan SlackEvent) { | |
160 | 110 | for { |
161 | 111 | event := json.RawMessage{} |
162 | if err := websocket.JSON.Receive(api.conn, &event); err == io.EOF { | |
112 | if err := websocket.JSON.Receive(ws.conn, &event); err == io.EOF { | |
163 | 113 | //log.Println("Derpi derp, should we destroy conn and start over?") |
164 | //if err = api.StartRTM(); err != nil { | |
114 | //if err = ws.StartRTM(); err != nil { | |
165 | 115 | // log.Fatal(err) |
166 | 116 | //} |
167 | 117 | // should we reconnect here? |
168 | if !api.conn.IsClientConn() { | |
169 | api.conn, err = websocket.Dial(api.info.URL, api.config.protocol, api.config.origin) | |
118 | ||
119 | if !ws.conn.IsClientConn() { | |
120 | ws.conn, err = websocket.Dial(ws.info.Url, "", "") | |
170 | 121 | if err != nil { |
171 | 122 | log.Panic(err) |
172 | 123 | } |
176 | 127 | log.Panic(err) |
177 | 128 | } |
178 | 129 | if len(event) == 0 { |
179 | if api.debug { | |
130 | if ws.debug { | |
180 | 131 | log.Println("Event Empty. WTF?") |
181 | 132 | } |
182 | 133 | } else { |
183 | if api.debug { | |
134 | if ws.debug { | |
184 | 135 | log.Println(string(event[:])) |
185 | 136 | } |
186 | api.handleEvent(ch, event) | |
137 | ws.handleEvent(ch, event) | |
187 | 138 | } |
188 | 139 | time.Sleep(time.Millisecond * 500) |
189 | 140 | } |
190 | 141 | } |
191 | 142 | |
192 | func (api *WS) handleEvent(ch chan SlackEvent, event json.RawMessage) { | |
143 | func (ws *SlackWS) handleEvent(ch chan SlackEvent, event json.RawMessage) { | |
193 | 144 | em := Event{} |
194 | 145 | err := json.Unmarshal(event, &em) |
195 | 146 | if err != nil { |
216 | 167 | if err = json.Unmarshal(event, &pong); err != nil { |
217 | 168 | log.Fatal(err) |
218 | 169 | } |
219 | api.mutex.Lock() | |
220 | latency := time.Since(api.pings[pong.ReplyTo]) | |
221 | api.mutex.Unlock() | |
170 | ws.mutex.Lock() | |
171 | latency := time.Since(ws.pings[pong.ReplyTo]) | |
172 | ws.mutex.Unlock() | |
222 | 173 | ch <- SlackEvent{"latency-report", LatencyReport{Value: latency}} |
223 | 174 | default: |
224 | 175 | callEvent(em.Type, ch, event) |
1 | 1 | |
2 | 2 | import "encoding/json" |
3 | 3 | |
4 | // TODO: Probably need an error event | |
4 | ||
5 | // AckMessage is used for messages received in reply to other messages | |
6 | type AckMessage struct { | |
7 | ReplyTo int `json:"reply_to"` | |
8 | Timestamp string `json:"ts"` | |
9 | Text string `json:"text"` | |
10 | SlackWSResponse | |
11 | } | |
12 | ||
13 | type SlackWSResponse struct { | |
14 | Ok bool `json:"ok"` | |
15 | Error *SlackWSError `json:"error"` | |
16 | } | |
17 | ||
18 | type SlackWSError struct { | |
19 | Code int | |
20 | Msg string | |
21 | } | |
22 | ||
23 | func (s SlackWSError) Error() string { | |
24 | return s.Msg | |
25 | } | |
26 | ||
27 | type MessageEvent Message | |
28 | ||
29 | // SlackEvent is the main wrapper. You will find all the other messages attached | |
30 | type SlackEvent struct { | |
31 | Type string | |
32 | Data interface{} | |
33 | } | |
5 | 34 | |
6 | 35 | // HelloEvent represents the hello event |
7 | 36 | type HelloEvent struct{} |
0 | package slack | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | "log" | |
5 | "net" | |
6 | "net/url" | |
7 | "strconv" | |
8 | "time" | |
9 | ) | |
10 | ||
11 | type JSONTimeString string | |
12 | ||
13 | // String converts the unix timestamp into a string | |
14 | func (t JSONTimeString) String() string { | |
15 | if t == "" { | |
16 | return "" | |
17 | } | |
18 | floatN, err := strconv.ParseFloat(string(t), 64) | |
19 | if err != nil { | |
20 | log.Panicln(err) | |
21 | return "" | |
22 | } | |
23 | timeStr := int64(floatN) | |
24 | tm := time.Unix(int64(timeStr), 0) | |
25 | return fmt.Sprintf("\"%s\"", tm.Format("Mon Jan _2")) | |
26 | } | |
27 | ||
28 | var portMapping = map[string]string{"ws": "80", "wss": "443"} | |
29 | ||
30 | func websocketizeUrlPort(orig string) (string, error) { | |
31 | urlObj, err := url.ParseRequestURI(orig) | |
32 | if err != nil { | |
33 | return "", err | |
34 | } | |
35 | _, _, err = net.SplitHostPort(urlObj.Host) | |
36 | if err != nil { | |
37 | return urlObj.Scheme + "://" + urlObj.Host + ":" + portMapping[urlObj.Scheme] + urlObj.Path, nil | |
38 | } | |
39 | return orig, nil | |
40 | } |