diff --git a/client-ctl.go b/client-ctl.go index d059578..f68dcd9 100644 --- a/client-ctl.go +++ b/client-ctl.go @@ -199,7 +199,7 @@ func (ctl *client_ctl_client_conns) ServeHTTP(w http.ResponseWriter, req *http.R } c.cts_mtx.Unlock() - status_code = write_json_resp_header(w, http.StatusOK) + status_code = WriteJsonRespHeader(w, http.StatusOK) if err = je.Encode(js); err != nil { goto oops } case http.MethodPost: @@ -215,7 +215,7 @@ func (ctl *client_ctl_client_conns) ServeHTTP(w http.ResponseWriter, req *http.R err = json.NewDecoder(req.Body).Decode(&s) if err != nil || len(s.ServerAddrs) <= 0 { - status_code = write_empty_resp_header(w, http.StatusBadRequest) + status_code = WriteEmptyRespHeader(w, http.StatusBadRequest) goto done } @@ -223,10 +223,10 @@ func (ctl *client_ctl_client_conns) ServeHTTP(w http.ResponseWriter, req *http.R //cc.PeerAddrs = s.PeerAddrs cts, err = c.start_service(&cc) // TODO: this can be blocking. do we have to resolve addresses before calling this? also not good because resolution succeed or fail at each attempt. however ok as ServeHTTP itself is in a goroutine? if err != nil { - status_code = write_json_resp_header(w, http.StatusInternalServerError) + status_code = WriteJsonRespHeader(w, http.StatusInternalServerError) if err = je.Encode(json_errmsg{Text: err.Error()}); err != nil { goto oops } } else { - status_code = write_json_resp_header(w, http.StatusCreated) + status_code = WriteJsonRespHeader(w, http.StatusCreated) if err = je.Encode(json_out_client_conn_id{Id: cts.id}); err != nil { goto oops } } @@ -236,10 +236,10 @@ func (ctl *client_ctl_client_conns) ServeHTTP(w http.ResponseWriter, req *http.R // we do passive deletion rather than doing active deletion by calling // c.RemoveAllClientConns() c.ReqStopAllClientConns() - status_code = write_empty_resp_header(w, http.StatusNoContent) + status_code = WriteEmptyRespHeader(w, http.StatusNoContent) default: - status_code = write_empty_resp_header(w, http.StatusBadRequest) + status_code = WriteEmptyRespHeader(w, http.StatusBadRequest) } done: @@ -268,14 +268,14 @@ func (ctl *client_ctl_client_conns_id) ServeHTTP(w http.ResponseWriter, req *htt conn_nid, err = strconv.ParseUint(conn_id, 10, int(unsafe.Sizeof(ConnId(0)) * 8)) if err != nil { - status_code = write_json_resp_header(w, http.StatusBadRequest) + status_code = WriteJsonRespHeader(w, http.StatusBadRequest) if err = je.Encode(json_errmsg{Text: "wrong connection id - " + conn_id}); err != nil { goto oops } goto done } cts = c.FindClientConnById(ConnId(conn_nid)) if cts == nil { - status_code = write_json_resp_header(w, http.StatusNotFound) + status_code = WriteJsonRespHeader(w, http.StatusNotFound) if err = je.Encode(json_errmsg{Text: "non-existent connection id - " + conn_id}); err != nil { goto oops } goto done } @@ -310,16 +310,16 @@ func (ctl *client_ctl_client_conns_id) ServeHTTP(w http.ResponseWriter, req *htt } cts.route_mtx.Unlock() - status_code = write_json_resp_header(w, http.StatusOK) + status_code = WriteJsonRespHeader(w, http.StatusOK) if err = je.Encode(js); err != nil { goto oops } case http.MethodDelete: //c.RemoveClientConn(cts) cts.ReqStop() - status_code = write_empty_resp_header(w, http.StatusNoContent) + status_code = WriteEmptyRespHeader(w, http.StatusNoContent) default: - status_code = write_empty_resp_header(w, http.StatusBadRequest) + status_code = WriteEmptyRespHeader(w, http.StatusBadRequest) } done: @@ -347,14 +347,14 @@ func (ctl *client_ctl_client_conns_id_routes) ServeHTTP(w http.ResponseWriter, r conn_nid, err = strconv.ParseUint(conn_id, 10, int(unsafe.Sizeof(ConnId(0)) * 8)) if err != nil { - status_code = write_json_resp_header(w, http.StatusBadRequest) + status_code = WriteJsonRespHeader(w, http.StatusBadRequest) if err = je.Encode(json_errmsg{Text: "wrong connection id - " + conn_id }); err != nil { goto oops } goto done } cts = c.FindClientConnById(ConnId(conn_nid)) if cts == nil { - status_code = write_json_resp_header(w, http.StatusNotFound) + status_code = WriteJsonRespHeader(w, http.StatusNotFound) if err = je.Encode(json_errmsg{Text: "non-existent connection id - " + conn_id}); err != nil { goto oops } goto done } @@ -380,7 +380,7 @@ func (ctl *client_ctl_client_conns_id_routes) ServeHTTP(w http.ResponseWriter, r } cts.route_mtx.Unlock() - status_code = write_json_resp_header(w, http.StatusOK) + status_code = WriteJsonRespHeader(w, http.StatusOK) if err = je.Encode(jsp); err != nil { goto oops } case http.MethodPost: @@ -392,26 +392,26 @@ func (ctl *client_ctl_client_conns_id_routes) ServeHTTP(w http.ResponseWriter, r err = json.NewDecoder(req.Body).Decode(&jcr) if err != nil { - status_code = write_empty_resp_header(w, http.StatusBadRequest) + status_code = WriteEmptyRespHeader(w, http.StatusBadRequest) goto oops } if jcr.ClientPeerAddr == "" { - status_code = write_empty_resp_header(w, http.StatusBadRequest) + status_code = WriteEmptyRespHeader(w, http.StatusBadRequest) err = fmt.Errorf("blank client-peer-addr") goto oops } server_peer_option = string_to_route_option(jcr.ServerPeerOption) if server_peer_option == RouteOption(ROUTE_OPTION_UNSPEC) { - status_code = write_empty_resp_header(w, http.StatusBadRequest) + status_code = WriteEmptyRespHeader(w, http.StatusBadRequest) err = fmt.Errorf("wrong server-peer-option value - %s", server_peer_option) goto oops } lifetime, err = parse_duration_string(jcr.Lifetime) if err != nil { - status_code = write_empty_resp_header(w, http.StatusBadRequest) + status_code = WriteEmptyRespHeader(w, http.StatusBadRequest) err = fmt.Errorf("wrong lifetime value %s - %s", jcr.Lifetime, err.Error()) goto oops } @@ -429,20 +429,20 @@ func (ctl *client_ctl_client_conns_id_routes) ServeHTTP(w http.ResponseWriter, r //cts.AddClientRouteConfig(rc) // TODO: this is to remember... but how to delete it? r, err = cts.AddNewClientRoute(rc) if err != nil { - status_code = write_json_resp_header(w, http.StatusInternalServerError) + status_code = WriteJsonRespHeader(w, http.StatusInternalServerError) if err = je.Encode(json_errmsg{Text: err.Error()}); err != nil { goto oops } } else { - status_code = write_json_resp_header(w, http.StatusCreated) + status_code = WriteJsonRespHeader(w, http.StatusCreated) if err = je.Encode(json_out_client_route_id{Id: r.id, CtsId: r.cts.id}); err != nil { goto oops } } case http.MethodDelete: //cts.RemoveAllClientRoutes() cts.ReqStopAllClientRoutes() - status_code = write_empty_resp_header(w, http.StatusNoContent) + status_code = WriteEmptyRespHeader(w, http.StatusNoContent) default: - status_code = write_empty_resp_header(w, http.StatusBadRequest) + status_code = WriteEmptyRespHeader(w, http.StatusBadRequest) } done: @@ -474,34 +474,34 @@ func (ctl *client_ctl_client_conns_id_routes_id) ServeHTTP(w http.ResponseWriter conn_nid, err = strconv.ParseUint(conn_id, 10, int(unsafe.Sizeof(ConnId(0)) * 8)) if err != nil { - status_code = write_json_resp_header(w, http.StatusBadRequest) + status_code = WriteJsonRespHeader(w, http.StatusBadRequest) if err = je.Encode(json_errmsg{Text: "wrong connection id - " + conn_id}); err != nil { goto oops } goto done } route_nid, err = strconv.ParseUint(route_id, 10, int(unsafe.Sizeof(RouteId(0)) * 8)) if err != nil { - status_code = write_json_resp_header(w, http.StatusBadRequest) + status_code = WriteJsonRespHeader(w, http.StatusBadRequest) if err = je.Encode(json_errmsg{Text: "wrong route id - " + route_id}); err != nil { goto oops } goto done } cts = c.FindClientConnById(ConnId(conn_nid)) if cts == nil { - status_code = write_json_resp_header(w, http.StatusNotFound) + status_code = WriteJsonRespHeader(w, http.StatusNotFound) if err = je.Encode(json_errmsg{Text: "non-existent connection id - " + conn_id}); err != nil { goto oops } goto done } r = cts.FindClientRouteById(RouteId(route_nid)) if r == nil { - status_code = write_json_resp_header(w, http.StatusNotFound) + status_code = WriteJsonRespHeader(w, http.StatusNotFound) if err = je.Encode(json_errmsg{Text: "non-existent route id - " + route_id}); err != nil { goto oops } goto done } switch req.Method { case http.MethodGet: - status_code = write_json_resp_header(w, http.StatusOK) + status_code = WriteJsonRespHeader(w, http.StatusOK) err = je.Encode(json_out_client_route{ Id: r.id, ClientPeerAddr: r.peer_addr, @@ -519,13 +519,13 @@ func (ctl *client_ctl_client_conns_id_routes_id) ServeHTTP(w http.ResponseWriter err = json.NewDecoder(req.Body).Decode(&jcr) if err != nil { - status_code = write_empty_resp_header(w, http.StatusBadRequest) + status_code = WriteEmptyRespHeader(w, http.StatusBadRequest) goto oops } lifetime, err = parse_duration_string(jcr.Lifetime) if err != nil { - status_code = write_empty_resp_header(w, http.StatusBadRequest) + status_code = WriteEmptyRespHeader(w, http.StatusBadRequest) err = fmt.Errorf("wrong lifetime value %s - %s", jcr.Lifetime, err.Error()) goto oops } @@ -536,18 +536,18 @@ func (ctl *client_ctl_client_conns_id_routes_id) ServeHTTP(w http.ResponseWriter err = r.ResetLifetime(lifetime) } if err != nil { - status_code = write_empty_resp_header(w, http.StatusForbidden) + status_code = WriteEmptyRespHeader(w, http.StatusForbidden) goto oops } - status_code = write_empty_resp_header(w, http.StatusOK) + status_code = WriteEmptyRespHeader(w, http.StatusOK) case http.MethodDelete: r.ReqStop() - status_code = write_empty_resp_header(w, http.StatusNoContent) + status_code = WriteEmptyRespHeader(w, http.StatusNoContent) default: - status_code = write_empty_resp_header(w, http.StatusBadRequest) + status_code = WriteEmptyRespHeader(w, http.StatusBadRequest) } done: @@ -579,34 +579,34 @@ func (ctl *client_ctl_client_conns_id_routes_spsp) ServeHTTP(w http.ResponseWrit conn_nid, err = strconv.ParseUint(conn_id, 10, int(unsafe.Sizeof(ConnId(0)) * 8)) if err != nil { - status_code = write_json_resp_header(w, http.StatusBadRequest) + status_code = WriteJsonRespHeader(w, http.StatusBadRequest) if err = je.Encode(json_errmsg{Text: "wrong connection id - " + conn_id}); err != nil { goto oops } goto done } port_nid, err = strconv.ParseUint(port_id, 10, int(unsafe.Sizeof(PortId(0)) * 8)) if err != nil { - status_code = write_json_resp_header(w, http.StatusBadRequest) + status_code = WriteJsonRespHeader(w, http.StatusBadRequest) if err = je.Encode(json_errmsg{Text: "wrong route id - " + port_id}); err != nil { goto oops } goto done } cts = c.FindClientConnById(ConnId(conn_nid)) if cts == nil { - status_code = write_json_resp_header(w, http.StatusNotFound) + status_code = WriteJsonRespHeader(w, http.StatusNotFound) if err = je.Encode(json_errmsg{Text: "non-existent connection id - " + conn_id}); err != nil { goto oops } goto done } r = cts.FindClientRouteByServerPeerSvcPortId(PortId(port_nid)) if r == nil { - status_code = write_json_resp_header(w, http.StatusNotFound) + status_code = WriteJsonRespHeader(w, http.StatusNotFound) if err = je.Encode(json_errmsg{Text: "non-existent server peer port id - " + port_id}); err != nil { goto oops } goto done } switch req.Method { case http.MethodGet: - status_code = write_json_resp_header(w, http.StatusOK) + status_code = WriteJsonRespHeader(w, http.StatusOK) err = je.Encode(json_out_client_route{ Id: r.id, ClientPeerAddr: r.peer_addr, @@ -624,17 +624,17 @@ func (ctl *client_ctl_client_conns_id_routes_spsp) ServeHTTP(w http.ResponseWrit err = json.NewDecoder(req.Body).Decode(&jcr) if err != nil { - status_code = write_empty_resp_header(w, http.StatusBadRequest) + status_code = WriteEmptyRespHeader(w, http.StatusBadRequest) goto oops } lifetime, err = parse_duration_string(jcr.Lifetime) if err != nil { - status_code = write_empty_resp_header(w, http.StatusBadRequest) + status_code = WriteEmptyRespHeader(w, http.StatusBadRequest) err = fmt.Errorf("wrong lifetime value %s - %s", jcr.Lifetime, err.Error()) goto oops } else if lifetime < 0 { - status_code = write_empty_resp_header(w, http.StatusBadRequest) + status_code = WriteEmptyRespHeader(w, http.StatusBadRequest) err = fmt.Errorf("negative lifetime value %s", jcr.Lifetime) goto oops } @@ -648,10 +648,10 @@ func (ctl *client_ctl_client_conns_id_routes_spsp) ServeHTTP(w http.ResponseWrit case http.MethodDelete: r.ReqStop() - status_code = write_empty_resp_header(w, http.StatusNoContent) + status_code = WriteEmptyRespHeader(w, http.StatusNoContent) default: - status_code = write_empty_resp_header(w, http.StatusBadRequest) + status_code = WriteEmptyRespHeader(w, http.StatusBadRequest) } done: @@ -682,20 +682,20 @@ func (ctl *client_ctl_client_conns_id_routes_id_peers) ServeHTTP(w http.Response conn_nid, err = strconv.ParseUint(conn_id, 10, int(unsafe.Sizeof(ConnId(0)) * 8)) if err != nil { - status_code = write_json_resp_header(w, http.StatusBadRequest) + status_code = WriteJsonRespHeader(w, http.StatusBadRequest) if err = je.Encode(json_errmsg{Text: "wrong connection id - " + conn_id}); err != nil { goto oops } goto done } route_nid, err = strconv.ParseUint(route_id, 10, int(unsafe.Sizeof(RouteId(0)) * 8)) if err != nil { - status_code = write_json_resp_header(w, http.StatusBadRequest) + status_code = WriteJsonRespHeader(w, http.StatusBadRequest) if err = je.Encode(json_errmsg{Text: "wrong route id - " + route_id}); err != nil { goto oops } goto done } r = c.FindClientRouteById(ConnId(conn_nid), RouteId(route_nid)) if r == nil { - status_code = write_json_resp_header(w, http.StatusNotFound) + status_code = WriteJsonRespHeader(w, http.StatusNotFound) if err = je.Encode(json_errmsg{Text: "non-existent connection/route id - " + conn_id + "/" + route_id}); err != nil { goto oops } goto done } @@ -718,15 +718,15 @@ func (ctl *client_ctl_client_conns_id_routes_id_peers) ServeHTTP(w http.Response } r.ptc_mtx.Unlock() - status_code = write_json_resp_header(w, http.StatusOK) + status_code = WriteJsonRespHeader(w, http.StatusOK) if err = je.Encode(jcp); err != nil { goto oops } case http.MethodDelete: r.ReqStopAllClientPeerConns() - status_code = write_empty_resp_header(w, http.StatusNoContent) + status_code = WriteEmptyRespHeader(w, http.StatusNoContent) default: - status_code = write_empty_resp_header(w, http.StatusBadRequest) + status_code = WriteEmptyRespHeader(w, http.StatusBadRequest) } done: @@ -760,26 +760,26 @@ func (ctl *client_ctl_client_conns_id_routes_id_peers_id) ServeHTTP(w http.Respo conn_nid, err = strconv.ParseUint(conn_id, 10, int(unsafe.Sizeof(ConnId(0)) * 8)) if err != nil { - status_code = write_json_resp_header(w, http.StatusBadRequest) + status_code = WriteJsonRespHeader(w, http.StatusBadRequest) if err = je.Encode(json_errmsg{Text: "wrong connection id - " + conn_id}); err != nil { goto oops } goto done } route_nid, err = strconv.ParseUint(route_id, 10, int(unsafe.Sizeof(RouteId(0)) * 8)) if err != nil { - status_code = write_json_resp_header(w, http.StatusBadRequest) + status_code = WriteJsonRespHeader(w, http.StatusBadRequest) if err = je.Encode(json_errmsg{Text: "wrong route id - " + route_id}); err != nil { goto oops } goto done } peer_nid, err = strconv.ParseUint(peer_id, 10, int(unsafe.Sizeof(ConnId(0)) * 8)) if err != nil { - status_code = write_json_resp_header(w, http.StatusBadRequest) + status_code = WriteJsonRespHeader(w, http.StatusBadRequest) if err = je.Encode(json_errmsg{Text: "wrong peer id - " + peer_id}); err != nil { goto oops } goto done } p = c.FindClientPeerConnById(ConnId(conn_nid), RouteId(route_nid), PeerId(peer_nid)) if p == nil { - status_code = write_json_resp_header(w, http.StatusNotFound) + status_code = WriteJsonRespHeader(w, http.StatusNotFound) if err = je.Encode(json_errmsg{Text: "non-existent connection/route/peer id - " + conn_id + "/" + route_id + "/" + peer_id}); err != nil { goto oops } goto done } @@ -796,15 +796,15 @@ func (ctl *client_ctl_client_conns_id_routes_id_peers_id) ServeHTTP(w http.Respo ServerLocalAddr: p.pts_laddr, } - status_code = write_json_resp_header(w, http.StatusOK) + status_code = WriteJsonRespHeader(w, http.StatusOK) if err = je.Encode(jcp); err != nil { goto oops } case http.MethodDelete: p.ReqStop() - status_code = write_empty_resp_header(w, http.StatusNoContent) + status_code = WriteEmptyRespHeader(w, http.StatusNoContent) default: - status_code = write_empty_resp_header(w, http.StatusBadRequest) + status_code = WriteEmptyRespHeader(w, http.StatusBadRequest) } done: @@ -839,11 +839,11 @@ func (ctl *client_ctl_stats) ServeHTTP(w http.ResponseWriter, req *http.Request) stats.ClientConns = c.stats.conns.Load() stats.ClientRoutes = c.stats.routes.Load() stats.ClientPeers = c.stats.peers.Load() - status_code = write_json_resp_header(w, http.StatusOK) + status_code = WriteJsonRespHeader(w, http.StatusOK) if err = je.Encode(stats); err != nil { goto oops } default: - status_code = write_empty_resp_header(w, http.StatusBadRequest) + status_code = WriteEmptyRespHeader(w, http.StatusBadRequest) } //done: diff --git a/client.go b/client.go index bd0c994..83cdfa3 100644 --- a/client.go +++ b/client.go @@ -410,7 +410,7 @@ func (r *ClientRoute) ConnectToPeer(pts_id PeerId, route_option RouteOption, pts r.ptc_mtx.Unlock() d.LocalAddr = nil // TOOD: use this if local address is specified - conn, err = d.DialContext(waitctx, "tcp", r.peer_addr) + conn, err = d.DialContext(waitctx, TcpAddrStrClass(r.peer_addr), r.peer_addr) r.ptc_mtx.Lock() cancel_wait() @@ -500,7 +500,7 @@ func (r *ClientRoute) ReportEvent(pts_id PeerId, event_type PACKET_KIND, event_d r.ReqStop() } else { var addr *net.TCPAddr - addr, err = net.ResolveTCPAddr("tcp", rd.TargetAddrStr) + addr, err = net.ResolveTCPAddr(TcpAddrStrClass(rd.TargetAddrStr), rd.TargetAddrStr) if err != nil { r.cts.cli.log.Write(r.cts.sid, LOG_ERROR, "Protocol error - invalid service address(%s) for server peer in route_started event(%d)", rd.TargetAddrStr, r.id) r.ReqStop() @@ -1260,21 +1260,21 @@ func NewClient(ctx context.Context, logger Logger, ctl_addrs []string, ctl_prefi c.ctl_mux = http.NewServeMux() c.ctl_mux.Handle(c.ctl_prefix + "/_ctl/client-conns", - c.wrap_http_handler(&client_ctl_client_conns{client_ctl{c: &c, id: "ctl"}})) + c.wrap_http_handler(&client_ctl_client_conns{client_ctl{c: &c, id: HS_ID_CTL}})) c.ctl_mux.Handle(c.ctl_prefix + "/_ctl/client-conns/{conn_id}", - c.wrap_http_handler(&client_ctl_client_conns_id{client_ctl{c: &c, id: "ctl"}})) + c.wrap_http_handler(&client_ctl_client_conns_id{client_ctl{c: &c, id: HS_ID_CTL}})) c.ctl_mux.Handle(c.ctl_prefix + "/_ctl/client-conns/{conn_id}/routes", - c.wrap_http_handler(&client_ctl_client_conns_id_routes{client_ctl{c: &c, id: "ctl"}})) + c.wrap_http_handler(&client_ctl_client_conns_id_routes{client_ctl{c: &c, id: HS_ID_CTL}})) c.ctl_mux.Handle(c.ctl_prefix + "/_ctl/client-conns/{conn_id}/routes/{route_id}", - c.wrap_http_handler(&client_ctl_client_conns_id_routes_id{client_ctl{c: &c, id: "ctl"}})) + c.wrap_http_handler(&client_ctl_client_conns_id_routes_id{client_ctl{c: &c, id: HS_ID_CTL}})) c.ctl_mux.Handle(c.ctl_prefix + "/_ctl/client-conns/{conn_id}/routes-spsp/{port_id}", - c.wrap_http_handler(&client_ctl_client_conns_id_routes_spsp{client_ctl{c: &c, id: "ctl"}})) + c.wrap_http_handler(&client_ctl_client_conns_id_routes_spsp{client_ctl{c: &c, id: HS_ID_CTL}})) c.ctl_mux.Handle(c.ctl_prefix + "/_ctl/client-conns/{conn_id}/routes/{route_id}/peers", - c.wrap_http_handler(&client_ctl_client_conns_id_routes_id_peers{client_ctl{c: &c, id: "ctl"}})) + c.wrap_http_handler(&client_ctl_client_conns_id_routes_id_peers{client_ctl{c: &c, id: HS_ID_CTL}})) c.ctl_mux.Handle(c.ctl_prefix + "/_ctl/client-conns/{conn_id}/routes/{route_id}/peers/{peer_id}", - c.wrap_http_handler(&client_ctl_client_conns_id_routes_id_peers_id{client_ctl{c: &c, id: "ctl"}})) + c.wrap_http_handler(&client_ctl_client_conns_id_routes_id_peers_id{client_ctl{c: &c, id: HS_ID_CTL}})) c.ctl_mux.Handle(c.ctl_prefix + "/_ctl/stats", - c.wrap_http_handler(&client_ctl_stats{client_ctl{c: &c, id: "ctl"}})) + c.wrap_http_handler(&client_ctl_stats{client_ctl{c: &c, id: HS_ID_CTL}})) c.ctl_addr = make([]string, len(ctl_addrs)) c.ctl = make([]*http.Server, len(ctl_addrs)) diff --git a/hodu.go b/hodu.go index 07c6091..ae3f025 100644 --- a/hodu.go +++ b/hodu.go @@ -60,6 +60,14 @@ func TcpAddrStrClass(addr string) string { return "tcp" } +func TcpAddrClass(addr *net.TCPAddr) string { + if addr.AddrPort().Addr().Is4() { + return "tcp4" + } else { + return "tcp6" + } +} + func word_to_route_option(word string) RouteOption { switch word { case "tcp4": @@ -158,7 +166,7 @@ func parse_duration_string(dur string) (time.Duration, error) { return time.ParseDuration(tmp) } -func write_json_resp_header(w http.ResponseWriter, status_code int) int { +func WriteJsonRespHeader(w http.ResponseWriter, status_code int) int { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status_code) return status_code @@ -170,19 +178,19 @@ func write_js_resp_header(w http.ResponseWriter, status_code int) int { return status_code } -func write_css_resp_header(w http.ResponseWriter, status_code int) int { +func WriteCssRespHeader(w http.ResponseWriter, status_code int) int { w.Header().Set("Content-Type", "text/css") w.WriteHeader(status_code) return status_code } -func write_html_resp_header(w http.ResponseWriter, status_code int) int { +func WriteHtmlRespHeader(w http.ResponseWriter, status_code int) int { w.Header().Set("Content-Type", "text/html") w.WriteHeader(status_code) return status_code } -func write_empty_resp_header(w http.ResponseWriter, status_code int) int { +func WriteEmptyRespHeader(w http.ResponseWriter, status_code int) int { w.WriteHeader(status_code) return status_code } diff --git a/server-ctl.go b/server-ctl.go index 4132a79..3b5e94f 100644 --- a/server-ctl.go +++ b/server-ctl.go @@ -115,15 +115,15 @@ func (ctl *server_ctl_server_conns) ServeHTTP(w http.ResponseWriter, req *http.R } s.cts_mtx.Unlock() - status_code = write_json_resp_header(w, http.StatusOK) + status_code = WriteJsonRespHeader(w, http.StatusOK) if err = je.Encode(js); err != nil { goto oops } case http.MethodDelete: s.ReqStopAllServerConns() - status_code = write_empty_resp_header(w, http.StatusNoContent) + status_code = WriteEmptyRespHeader(w, http.StatusNoContent) default: - status_code = write_empty_resp_header(w, http.StatusBadRequest) + status_code = WriteEmptyRespHeader(w, http.StatusBadRequest) } //done: @@ -149,7 +149,7 @@ func (ctl *server_ctl_server_conns_id) ServeHTTP(w http.ResponseWriter, req *htt conn_id = req.PathValue("conn_id") cts, err = s.FindServerConnByIdStr(conn_id) if err != nil { - status_code = write_json_resp_header(w, http.StatusNotFound) + status_code = WriteJsonRespHeader(w, http.StatusNotFound) if err = je.Encode(json_errmsg{Text: err.Error()}); err != nil { goto oops } goto done } @@ -180,16 +180,16 @@ func (ctl *server_ctl_server_conns_id) ServeHTTP(w http.ResponseWriter, req *htt } cts.route_mtx.Unlock() - status_code = write_json_resp_header(w, http.StatusOK) + status_code = WriteJsonRespHeader(w, http.StatusOK) if err = je.Encode(js); err != nil { goto oops } case http.MethodDelete: //s.RemoveServerConn(cts) cts.ReqStop() - status_code = write_empty_resp_header(w, http.StatusNoContent) + status_code = WriteEmptyRespHeader(w, http.StatusNoContent) default: - status_code = write_empty_resp_header(w, http.StatusBadRequest) + status_code = WriteEmptyRespHeader(w, http.StatusBadRequest) } done: @@ -215,7 +215,7 @@ func (ctl *server_ctl_server_conns_id_routes) ServeHTTP(w http.ResponseWriter, r conn_id = req.PathValue("conn_id") cts, err = s.FindServerConnByIdStr(conn_id) if err != nil { - status_code = write_json_resp_header(w, http.StatusNotFound) + status_code = WriteJsonRespHeader(w, http.StatusNotFound) if err = je.Encode(json_errmsg{Text: err.Error()}); err != nil { goto oops } goto done } @@ -239,16 +239,16 @@ func (ctl *server_ctl_server_conns_id_routes) ServeHTTP(w http.ResponseWriter, r } cts.route_mtx.Unlock() - status_code = write_json_resp_header(w, http.StatusOK) + status_code = WriteJsonRespHeader(w, http.StatusOK) if err = je.Encode(jsp); err != nil { goto oops } case http.MethodDelete: //cts.RemoveAllServerRoutes() cts.ReqStopAllServerRoutes() - status_code = write_empty_resp_header(w, http.StatusNoContent) + status_code = WriteEmptyRespHeader(w, http.StatusNoContent) default: - status_code = write_empty_resp_header(w, http.StatusBadRequest) + status_code = WriteEmptyRespHeader(w, http.StatusBadRequest) } done: @@ -272,32 +272,47 @@ func (ctl *server_ctl_server_conns_id_routes_id) ServeHTTP(w http.ResponseWriter s = ctl.s je = json.NewEncoder(w) - if ctl.id == "wpx" && req.Method != http.MethodGet { + if ctl.id == HS_ID_WPX && req.Method != http.MethodGet { // support the get method only, if invoked via the wpx endpoint - status_code = write_empty_resp_header(w, http.StatusBadRequest) + status_code = WriteEmptyRespHeader(w, http.StatusBadRequest) goto done } conn_id = req.PathValue("conn_id") route_id = req.PathValue("route_id") r, err = s.FindServerRouteByIdStr(conn_id, route_id) - if err != nil && ctl.id == "wpx" && route_id == PORT_ID_MARKER && ctl.s.wpx_foreign_port_proxy_maker != nil { - var pi *ServerRouteProxyInfo - // currenly, this is invoked via wpx only for ssh from xterm.html - // ugly, but hard-code the type to "ssh" here for now... - pi, err = ctl.s.wpx_foreign_port_proxy_maker("ssh", conn_id) - if err == nil { r = proxy_info_to_server_route(pi) } // fake route + if err != nil { + /* + if route_id == PORT_ID_MARKER && ctl.s.wpx_foreign_port_proxy_marker != nil { + // don't care if the ctl call is from wpx or not. if the request + // is by the port number(noted by route being PORT_ID_MARKER), + // check if it's a foreign port + var pi *ServerRouteProxyInfo + // currenly, this is invoked via wpx only for ssh from xterm.html + // ugly, but hard-code the type to "ssh" here for now... + pi, err = ctl.s.wpx_foreign_port_proxy_maker("ssh", conn_id) + if err == nil { r = proxy_info_to_server_route(pi) } // fake route + } + */ + + if ctl.id == HS_ID_WPX && route_id == PORT_ID_MARKER && ctl.s.wpx_foreign_port_proxy_maker != nil { + var pi *ServerRouteProxyInfo + // currenly, this is invoked via wpx only for ssh from xterm.html + // ugly, but hard-code the type to "ssh" here for now... + pi, err = ctl.s.wpx_foreign_port_proxy_maker("ssh", conn_id) + if err == nil { r = proxy_info_to_server_route(pi) } // fake route + } } if err != nil { - status_code = write_json_resp_header(w, http.StatusNotFound) + status_code = WriteJsonRespHeader(w, http.StatusNotFound) if err = je.Encode(json_errmsg{Text: err.Error()}); err != nil { goto oops } goto done } switch req.Method { case http.MethodGet: - status_code = write_json_resp_header(w, http.StatusOK) + status_code = WriteJsonRespHeader(w, http.StatusOK) err = je.Encode(json_out_server_route{ Id: r.Id, ClientPeerAddr: r.PtcAddr, @@ -309,11 +324,17 @@ func (ctl *server_ctl_server_conns_id_routes_id) ServeHTTP(w http.ResponseWriter if err != nil { goto oops } case http.MethodDelete: - r.ReqStop() - status_code = write_empty_resp_header(w, http.StatusNoContent) + /*if r is foreign { + // foreign route + ctl.s.wpx_foreign_port_proxy_stopper(conn_id) + } else {*/ + // native route + r.ReqStop() + //} + status_code = WriteEmptyRespHeader(w, http.StatusNoContent) default: - status_code = write_empty_resp_header(w, http.StatusBadRequest) + status_code = WriteEmptyRespHeader(w, http.StatusBadRequest) } done: @@ -355,11 +376,11 @@ func (ctl *server_ctl_stats) ServeHTTP(w http.ResponseWriter, req *http.Request) stats.Extra = make(map[string]int64, len(s.stats.extra)) for k, v = range s.stats.extra { stats.Extra[k] = v } s.stats.extra_mtx.Unlock() - status_code = write_json_resp_header(w, http.StatusOK) + status_code = WriteJsonRespHeader(w, http.StatusOK) if err = je.Encode(stats); err != nil { goto oops } default: - status_code = write_empty_resp_header(w, http.StatusBadRequest) + status_code = WriteEmptyRespHeader(w, http.StatusBadRequest) } //done: diff --git a/server-proxy.go b/server-proxy.go index 85e4b1c..6dd9f58 100644 --- a/server-proxy.go +++ b/server-proxy.go @@ -295,7 +295,7 @@ func (pxy *server_proxy_http_main) addr_to_transport (ctx context.Context, addr // establish the connection. dialer = &net.Dialer{} waitctx, cancel_wait = context.WithTimeout(ctx, 3 * time.Second) // TODO: make timeout configurable - conn, err = dialer.DialContext(waitctx, "tcp", addr.String()) + conn, err = dialer.DialContext(waitctx, TcpAddrClass(addr), addr.String()) cancel_wait() if err != nil { return nil, err } @@ -352,13 +352,13 @@ func (pxy *server_proxy_http_main) ServeHTTP(w http.ResponseWriter, req *http.Re pi, err = pxy.get_route_proxy_info(req, in_wpx_mode) if err != nil { - status_code = write_empty_resp_header(w, http.StatusNotFound) + status_code = WriteEmptyRespHeader(w, http.StatusNotFound) goto oops } /* if pi.SvcOption & (RouteOption(ROUTE_OPTION_HTTP) | RouteOption(ROUTE_OPTION_HTTPS)) == 0 { - status_code = write_empty_resp_header(w, http.StatusForbidden) + status_code = WriteEmptyRespHeader(w, http.StatusForbidden) err = fmt.Errorf("target not http/https") goto oops } @@ -366,7 +366,7 @@ func (pxy *server_proxy_http_main) ServeHTTP(w http.ResponseWriter, req *http.Re addr = svc_addr_to_dst_addr(pi.SvcAddr) transport, err = pxy.addr_to_transport(s.ctx, addr) if err != nil { - status_code = write_empty_resp_header(w, http.StatusBadGateway) + status_code = WriteEmptyRespHeader(w, http.StatusBadGateway) goto oops } proxy_url = pxy.req_to_proxy_url(req, pi) @@ -375,7 +375,7 @@ func (pxy *server_proxy_http_main) ServeHTTP(w http.ResponseWriter, req *http.Re proxy_req, err = http.NewRequestWithContext(s.ctx, req.Method, proxy_url.String(), req.Body) if err != nil { - status_code = write_empty_resp_header(w, http.StatusInternalServerError) + status_code = WriteEmptyRespHeader(w, http.StatusInternalServerError) goto oops } upgrade_required = mutate_proxy_req_headers(req, proxy_req, pi.PathPrefix, in_wpx_mode) @@ -392,7 +392,7 @@ func (pxy *server_proxy_http_main) ServeHTTP(w http.ResponseWriter, req *http.Re resp, err = client.Do(proxy_req) //resp, err = transport.RoundTrip(proxy_req) // any advantage if using RoundTrip instead? if err != nil { - status_code = write_empty_resp_header(w, http.StatusInternalServerError) + status_code = WriteEmptyRespHeader(w, http.StatusInternalServerError) goto oops } else { status_code = resp.StatusCode @@ -442,7 +442,7 @@ func (pxy *server_proxy_http_wpx) ServeHTTP(w http.ResponseWriter, req *http.Req var status_code int // var err error - status_code = write_empty_resp_header(w, http.StatusForbidden) + status_code = WriteEmptyRespHeader(w, http.StatusForbidden) // TODO: show the list of services running instead if enabled? @@ -474,7 +474,7 @@ func (pxy *server_proxy_xterm_file) ServeHTTP(w http.ResponseWriter, req *http.R status_code = write_js_resp_header(w, http.StatusOK) w.Write(xterm_addon_fit_js) case "xterm.css": - status_code = write_css_resp_header(w, http.StatusOK) + status_code = WriteCssRespHeader(w, http.StatusOK) w.Write(xterm_css) case "xterm.html": var tmpl *template.Template @@ -483,7 +483,7 @@ func (pxy *server_proxy_xterm_file) ServeHTTP(w http.ResponseWriter, req *http.R // this endpoint is registered for /_ssh/{conn_id}/{route_id}/ under pxy. // and for /_ssh/{port_id} under wpx. - if pxy.id == "wpx" { + if pxy.id == HS_ID_WPX { conn_id = req.PathValue("port_id") route_id = PORT_ID_MARKER _, err = s.FindServerRouteByIdStr(conn_id, route_id) @@ -496,7 +496,7 @@ func (pxy *server_proxy_xterm_file) ServeHTTP(w http.ResponseWriter, req *http.R _, err = s.FindServerRouteByIdStr(conn_id, route_id) } if err != nil { - status_code = write_empty_resp_header(w, http.StatusNotFound) + status_code = WriteEmptyRespHeader(w, http.StatusNotFound) goto oops } @@ -507,10 +507,10 @@ func (pxy *server_proxy_xterm_file) ServeHTTP(w http.ResponseWriter, req *http.R _, err = tmpl.Parse(xterm_html) } if err != nil { - status_code = write_empty_resp_header(w, http.StatusInternalServerError) + status_code = WriteEmptyRespHeader(w, http.StatusInternalServerError) goto oops } else { - status_code = write_html_resp_header(w, http.StatusOK) + status_code = WriteHtmlRespHeader(w, http.StatusOK) tmpl.Execute(w, &server_proxy_xterm_session_info{ ConnId: conn_id, @@ -526,10 +526,10 @@ func (pxy *server_proxy_xterm_file) ServeHTTP(w http.ResponseWriter, req *http.R w.WriteHeader(status_code) case "_forbidden": - status_code = write_empty_resp_header(w, http.StatusForbidden) + status_code = WriteEmptyRespHeader(w, http.StatusForbidden) default: - status_code = write_empty_resp_header(w, http.StatusNotFound) + status_code = WriteEmptyRespHeader(w, http.StatusNotFound) } //done: @@ -594,7 +594,7 @@ func (pxy *server_proxy_ssh_ws) connect_ssh (ctx context.Context, username strin addr = svc_addr_to_dst_addr(r.SvcAddr) dialer = &net.Dialer{} - conn, err = dialer.DialContext(ctx, "tcp", addr.String()) + conn, err = dialer.DialContext(ctx, TcpAddrClass(addr), addr.String()) if err != nil { goto oops } ssh_conn, chans, reqs, err = ssh.NewClientConn(conn, addr.String(), cc) diff --git a/server.go b/server.go index e4b8366..2bf4ca0 100644 --- a/server.go +++ b/server.go @@ -27,6 +27,10 @@ const CTS_LIMIT int = 16384 type PortId uint16 const PORT_ID_MARKER string = "_" +const HS_ID_CTL string = "ctl" +const HS_ID_WPX string = "wpx" +const HS_ID_PXY string = "pxy" +const HS_ID_PXY_WS string = "pxy-ws" type ServerConnMapByAddr = map[net.Addr]*ServerConn type ServerConnMap = map[ConnId]*ServerConn @@ -981,15 +985,14 @@ func NewServer(ctx context.Context, logger Logger, ctl_addrs []string, rpc_addrs /* create the specified number of listeners */ s.rpc = make([]*net.TCPListener, 0) for _, addr = range rpc_addrs { - rpcaddr, err = net.ResolveTCPAddr("tcp", addr) // Make this interruptable??? - if err != nil { - goto oops - } + var addr_class string - l, err = net.ListenTCP("tcp", rpcaddr) - if err != nil { - goto oops - } + addr_class = TcpAddrStrClass(addr) + rpcaddr, err = net.ResolveTCPAddr(addr_class, addr) // Make this interruptable??? + if err != nil { goto oops } + + l, err = net.ListenTCP(addr_class, rpcaddr) + if err != nil { goto oops } s.rpc = append(s.rpc, l) } @@ -1033,15 +1036,15 @@ func NewServer(ctx context.Context, logger Logger, ctl_addrs []string, rpc_addrs s.ctl_mux = http.NewServeMux() s.ctl_mux.Handle(s.ctl_prefix + "/_ctl/server-conns", - s.wrap_http_handler(&server_ctl_server_conns{server_ctl{s: &s, id: "ctl"}})) + s.wrap_http_handler(&server_ctl_server_conns{server_ctl{s: &s, id: HS_ID_CTL}})) s.ctl_mux.Handle(s.ctl_prefix + "/_ctl/server-conns/{conn_id}", - s.wrap_http_handler(&server_ctl_server_conns_id{server_ctl{s: &s, id: "ctl"}})) + s.wrap_http_handler(&server_ctl_server_conns_id{server_ctl{s: &s, id: HS_ID_CTL}})) s.ctl_mux.Handle(s.ctl_prefix + "/_ctl/server-conns/{conn_id}/routes", - s.wrap_http_handler(&server_ctl_server_conns_id_routes{server_ctl{s: &s, id: "ctl"}})) + s.wrap_http_handler(&server_ctl_server_conns_id_routes{server_ctl{s: &s, id: HS_ID_CTL}})) s.ctl_mux.Handle(s.ctl_prefix + "/_ctl/server-conns/{conn_id}/routes/{route_id}", - s.wrap_http_handler(&server_ctl_server_conns_id_routes_id{server_ctl{s: &s, id: "ctl"}})) + s.wrap_http_handler(&server_ctl_server_conns_id_routes_id{server_ctl{s: &s, id: HS_ID_CTL}})) s.ctl_mux.Handle(s.ctl_prefix + "/_ctl/stats", - s.wrap_http_handler(&server_ctl_stats{server_ctl{s: &s, id: "ctl"}})) + s.wrap_http_handler(&server_ctl_stats{server_ctl{s: &s, id: HS_ID_CTL}})) s.ctl_addr = make([]string, len(ctl_addrs)) s.ctl = make([]*http.Server, len(ctl_addrs)) @@ -1059,29 +1062,29 @@ func NewServer(ctx context.Context, logger Logger, ctl_addrs []string, rpc_addrs // --------------------------------------------------------- - s.pxy_ws = &server_proxy_ssh_ws{s: &s, id: "pxy-ws"} + s.pxy_ws = &server_proxy_ssh_ws{s: &s, id: HS_ID_PXY_WS} s.pxy_mux = http.NewServeMux() // TODO: make /_init,_ssh,_ssh_ws,_http configurable... s.pxy_mux.Handle("/_ssh-ws/{conn_id}/{route_id}", websocket.Handler(func(ws *websocket.Conn) { s.pxy_ws.ServeWebsocket(ws) })) s.pxy_mux.Handle("/_ssh/server-conns/{conn_id}/routes/{route_id}", - s.wrap_http_handler(&server_ctl_server_conns_id_routes_id{server_ctl{s: &s, id: "pxy"}})) + s.wrap_http_handler(&server_ctl_server_conns_id_routes_id{server_ctl{s: &s, id: HS_ID_PXY}})) s.pxy_mux.Handle("/_ssh/{conn_id}/", - s.wrap_http_handler(&server_proxy_xterm_file{server_proxy: server_proxy{s: &s, id: "pxy"}, file: "_redirect"})) + s.wrap_http_handler(&server_proxy_xterm_file{server_proxy: server_proxy{s: &s, id: HS_ID_PXY}, file: "_redirect"})) s.pxy_mux.Handle("/_ssh/{conn_id}/{route_id}/", - s.wrap_http_handler(&server_proxy_xterm_file{server_proxy: server_proxy{s: &s, id: "pxy"}, file: "xterm.html"})) + s.wrap_http_handler(&server_proxy_xterm_file{server_proxy: server_proxy{s: &s, id: HS_ID_PXY}, file: "xterm.html"})) s.pxy_mux.Handle("/_ssh/xterm.js", - s.wrap_http_handler(&server_proxy_xterm_file{server_proxy: server_proxy{s: &s, id: "pxy"}, file: "xterm.js"})) + s.wrap_http_handler(&server_proxy_xterm_file{server_proxy: server_proxy{s: &s, id: HS_ID_PXY}, file: "xterm.js"})) s.pxy_mux.Handle("/_ssh/xterm-addon-fit.js", - s.wrap_http_handler(&server_proxy_xterm_file{server_proxy: server_proxy{s: &s, id: "pxy"}, file: "xterm-addon-fit.js"})) + s.wrap_http_handler(&server_proxy_xterm_file{server_proxy: server_proxy{s: &s, id: HS_ID_PXY}, file: "xterm-addon-fit.js"})) s.pxy_mux.Handle("/_ssh/xterm.css", - s.wrap_http_handler(&server_proxy_xterm_file{server_proxy: server_proxy{s: &s, id: "pxy"}, file: "xterm.css"})) + s.wrap_http_handler(&server_proxy_xterm_file{server_proxy: server_proxy{s: &s, id: HS_ID_PXY}, file: "xterm.css"})) s.pxy_mux.Handle("/_ssh/", - s.wrap_http_handler(&server_proxy_xterm_file{server_proxy: server_proxy{s: &s, id: "pxy"}, file: "_forbidden"})) + s.wrap_http_handler(&server_proxy_xterm_file{server_proxy: server_proxy{s: &s, id: HS_ID_PXY}, file: "_forbidden"})) s.pxy_mux.Handle("/favicon.ico", - s.wrap_http_handler(&server_proxy_xterm_file{server_proxy: server_proxy{s: &s, id: "pxy"}, file: "_forbidden"})) + s.wrap_http_handler(&server_proxy_xterm_file{server_proxy: server_proxy{s: &s, id: HS_ID_PXY}, file: "_forbidden"})) s.pxy_mux.Handle("/_http/{conn_id}/{route_id}/{trailer...}", - s.wrap_http_handler(&server_proxy_http_main{server_proxy: server_proxy{s: &s, id: "pxy"}, prefix: "/_http"})) + s.wrap_http_handler(&server_proxy_http_main{server_proxy: server_proxy{s: &s, id: HS_ID_PXY}, prefix: "/_http"})) s.pxy_addr = make([]string, len(pxy_addrs)) s.pxy = make([]*http.Server, len(pxy_addrs)) @@ -1107,22 +1110,22 @@ func NewServer(ctx context.Context, logger Logger, ctl_addrs []string, rpc_addrs websocket.Handler(func(ws *websocket.Conn) { s.wpx_ws.ServeWebsocket(ws) })) s.wpx_mux.Handle("/_ssh/server-conns/{conn_id}/routes/{route_id}", - s.wrap_http_handler(&server_ctl_server_conns_id_routes_id{server_ctl{s: &s, id: "wpx"}})) + s.wrap_http_handler(&server_ctl_server_conns_id_routes_id{server_ctl{s: &s, id: HS_ID_WPX}})) s.wpx_mux.Handle("/_ssh/xterm.js", - s.wrap_http_handler(&server_proxy_xterm_file{server_proxy: server_proxy{s: &s, id: "wpx"}, file: "xterm.js"})) + s.wrap_http_handler(&server_proxy_xterm_file{server_proxy: server_proxy{s: &s, id: HS_ID_WPX}, file: "xterm.js"})) s.wpx_mux.Handle("/_ssh/xterm-addon-fit.js", - s.wrap_http_handler(&server_proxy_xterm_file{server_proxy: server_proxy{s: &s, id: "wpx"}, file: "xterm-addon-fit.js"})) + s.wrap_http_handler(&server_proxy_xterm_file{server_proxy: server_proxy{s: &s, id: HS_ID_WPX}, file: "xterm-addon-fit.js"})) s.wpx_mux.Handle("/_ssh/xterm.css", - s.wrap_http_handler(&server_proxy_xterm_file{server_proxy: server_proxy{s: &s, id: "wpx"}, file: "xterm.css"})) + s.wrap_http_handler(&server_proxy_xterm_file{server_proxy: server_proxy{s: &s, id: HS_ID_WPX}, file: "xterm.css"})) s.wpx_mux.Handle("/_ssh/{port_id}", - s.wrap_http_handler(&server_proxy_xterm_file{server_proxy: server_proxy{s: &s, id: "wpx"}, file: "xterm.html"})) + s.wrap_http_handler(&server_proxy_xterm_file{server_proxy: server_proxy{s: &s, id: HS_ID_WPX}, file: "xterm.html"})) s.wpx_mux.Handle("/_ssh/", - s.wrap_http_handler(&server_proxy_xterm_file{server_proxy: server_proxy{s: &s, id: "wpx"}, file: "_forbidden"})) + s.wrap_http_handler(&server_proxy_xterm_file{server_proxy: server_proxy{s: &s, id: HS_ID_WPX}, file: "_forbidden"})) s.wpx_mux.Handle("/{port_id}/{trailer...}", - s.wrap_http_handler(&server_proxy_http_main{server_proxy: server_proxy{s: &s, id: "wpx"}, prefix: PORT_ID_MARKER})) + s.wrap_http_handler(&server_proxy_http_main{server_proxy: server_proxy{s: &s, id: HS_ID_WPX}, prefix: PORT_ID_MARKER})) s.wpx_mux.Handle("/", - s.wrap_http_handler(&server_proxy_http_wpx{server_proxy: server_proxy{s: &s, id: "wpx"}})) + s.wrap_http_handler(&server_proxy_http_wpx{server_proxy: server_proxy{s: &s, id: HS_ID_WPX}})) s.wpx_addr = make([]string, len(wpx_addrs)) s.wpx = make([]*http.Server, len(wpx_addrs)) @@ -1663,3 +1666,7 @@ func (s *Server) SetExtraStat(key string, val int64) { s.stats.extra[key] = val s.stats.extra_mtx.Unlock() } + +func (s *Server) AddCtlHandler(path string, handler ServerHttpHandler) { + s.ctl_mux.Handle(s.ctl_prefix + "/_ctl" + path, s.wrap_http_handler(handler)) +}