MCPcopy
hub / github.com/OpenNHP/opennhp / handleRelay

Method handleRelay

endpoints/relay/relay.go:853–1010  ·  view source on GitHub ↗

handleRelay is the main relay endpoint. Expected request: POST /relay/{clusterId} Content-Type: application/octet-stream Body: raw inner NHP packet bytes (KNK / RKN / etc., encrypted by agent) Response: 200 OK — body contains raw NHP ACK / COK packet bytes (encrypted to agent) 400 Bad Requ

(w http.ResponseWriter, r *http.Request)

Source from the content-addressed store, hash-verified

851// 504 Gateway Timeout — NHP Server did not respond in time
852// 502 Bad Gateway — internal error
853func (rs *RelayServer) handleRelay(w http.ResponseWriter, r *http.Request) {
854 if r.Method != http.MethodPost {
855 http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
856 return
857 }
858
859 cr, status, errMsg := rs.resolveCluster(r)
860 if cr == nil {
861 http.Error(w, errMsg, status)
862 return
863 }
864
865 // Read inner NHP packet from request body. Cap at maxPacketSize+1 so we
866 // can reject oversize bodies without pulling an unbounded amount into
867 // memory. A single r.Body.Read() is not guaranteed to return the full
868 // payload; io.ReadAll drains until EOF.
869 innerPacket, err := io.ReadAll(io.LimitReader(r.Body, int64(maxPacketSize)+1))
870 if err != nil {
871 http.Error(w, "failed to read body", http.StatusBadRequest)
872 return
873 }
874 if len(innerPacket) == 0 {
875 http.Error(w, "empty packet", http.StatusBadRequest)
876 return
877 }
878 if len(innerPacket) > maxPacketSize {
879 http.Error(w, "packet too large", http.StatusBadRequest)
880 return
881 }
882 n := len(innerPacket)
883
884 // Extract the counter from the inner packet header (bytes [16:24], big-endian uint64).
885 // The NHP server echoes this counter in its ACK/COK response, so we use it
886 // to match the response back to this HTTP request.
887 if n < 24 {
888 http.Error(w, "inner packet too short", http.StatusBadRequest)
889 return
890 }
891 innerCounter := binary.BigEndian.Uint64(innerPacket[16:24])
892
893 // Body is valid; pick a target instance. normalize() blocks an empty
894 // instance list in phase 1, but phase 2's health checks can legitimately
895 // empty the healthy pool, so surface that as a 503 instead of panicking
896 // on a nil dereference downstream.
897 inst := cr.pickInstance()
898 if inst == nil {
899 http.Error(w, "cluster has no usable instance", http.StatusServiceUnavailable)
900 return
901 }
902
903 realAddr, err := realClientAddr(r)
904 if err != nil {
905 log.Error("[Relay] %v", err)
906 http.Error(w, "relay misconfigured: missing X-Real-IP header from local reverse proxy", http.StatusBadGateway)
907 return
908 }
909 realAddrKey := realAddr.String()
910 log.Info("[Relay] forwarding %d-byte inner packet (counter=%d, cluster=%s) from client %s to %s",

Calls 9

resolveClusterMethod · 0.95
realClientAddrFunction · 0.85
pickInstanceMethod · 0.80
NextCounterIndexMethod · 0.80
SetMethod · 0.80
HeaderMethod · 0.80
ErrorMethod · 0.65
StringMethod · 0.45
WriteMethod · 0.45