// mainLoop initiates all ports and handles the traffic func mainLoop() { openPorts() defer closePorts() waitCh := make(chan bool) go func() { total := 0 for { v := <-outCh if !v { log.Println("An OUT port is closed. Interrupting execution") exitCh <- syscall.SIGTERM break } else { total++ } // At least one output ports are opened if total >= 1 && waitCh != nil { waitCh <- true } } }() log.Println("Waiting for port connections to establish... ") select { case <-waitCh: log.Println("Ports connected") waitCh = nil case <-time.Tick(30 * time.Second): log.Println("Timeout: port connections were not established within provided interval") os.Exit(1) } // Setup socket poll items poller := zmq.NewPoller() poller.Add(intPort, zmq.POLLIN) poller.Add(reqPort, zmq.POLLIN) poller.Add(tmplPort, zmq.POLLIN) // This is obviously dangerous but we need it to deal with our custom CA's tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } client := &http.Client{Transport: tr} client.Timeout = 30 * time.Second var ( interval time.Duration ip [][]byte request *requestIP propTemplate *caf.PropertyTemplate httpRequest *http.Request ) for { sockets, err := poller.Poll(-1) if err != nil { log.Println("Error polling ports:", err.Error()) continue } for _, socket := range sockets { if socket.Socket == nil { log.Println("ERROR: could not find socket in polling items array") continue } ip, err = socket.Socket.RecvMessageBytes(0) if err != nil { log.Println("Error receiving message:", err.Error()) continue } if !runtime.IsValidIP(ip) || !runtime.IsPacket(ip) { log.Println("Invalid IP:", ip) continue } switch socket.Socket { case intPort: interval, err = time.ParseDuration(string(ip[1])) log.Println("Interval specified:", interval) case reqPort: err = json.Unmarshal(ip[1], &request) if err != nil { log.Println("ERROR: failed to unmarshal request:", err.Error()) continue } log.Println("Request specified:", request) case tmplPort: err = json.Unmarshal(ip[1], &propTemplate) if err != nil { log.Println("ERROR: failed to unmarshal template:", err.Error()) continue } log.Printf("Template specified: %+v", propTemplate) default: log.Println("ERROR: IP from unhandled socket received!") continue } } if interval > 0 && request != nil && propTemplate != nil { log.Println("Component configured. Moving on...") break } } log.Println("Started...") ticker := time.NewTicker(interval) for _ = range ticker.C { httpRequest, err = http.NewRequest(request.Method, request.URL, nil) utils.AssertError(err) // Set the accepted Content-Type if request.ContentType != "" { httpRequest.Header.Add("Content-Type", request.ContentType) } // Set any additional headers if provided for k, v := range request.Headers { httpRequest.Header.Add(k, v[0]) } response, err := client.Do(httpRequest) if err != nil { log.Printf("ERROR performing HTTP %s %s: %s\n", request.Method, request.URL, err.Error()) if errPort != nil { errPort.SendMessageDontwait(runtime.NewPacket([]byte(err.Error()))) } continue } resp, err := httputils.Response2Response(response) if err != nil { log.Println("ERROR converting response to reply:", err.Error()) if errPort != nil { errPort.SendMessageDontwait(runtime.NewPacket([]byte(err.Error()))) } continue } // Property output socket if propPort != nil { var data interface{} if strings.HasSuffix(request.ContentType, "json") { err = json.Unmarshal(resp.Body, &data) if err != nil { log.Println("ERROR unmarshaling the JSON response:", err.Error()) continue } } else { // TODO: support other content-types log.Printf("WARNING processing of %s is not supported", request.ContentType) continue } prop, err := propTemplate.Fill(data) if err != nil { log.Println("ERROR filling template with data: ", err.Error()) continue } out, _ := json.Marshal(prop) propPort.SendMessage(runtime.NewPacket(out)) } // Extra output sockets (e.g., for debugging) if respPort != nil { ip, err = httputils.Response2IP(resp) if err != nil { log.Println("ERROR converting reply to IP:", err.Error()) if errPort != nil { errPort.SendMessageDontwait(runtime.NewPacket([]byte(err.Error()))) } } else { respPort.SendMessage(ip) } } if bodyPort != nil { bodyPort.SendMessage(runtime.NewPacket(resp.Body)) } } }
// mainLoop initiates all ports and handles the traffic func mainLoop() { openPorts() defer closePorts() ports := 1 if bodyPort != nil { ports++ } if respPort != nil { ports++ } if errPort != nil { ports++ } waitCh := make(chan bool) reqExitCh := make(chan bool, 1) go func(num int) { total := 0 for { select { case v := <-reqCh: if v { total++ } else { reqExitCh <- true } case v := <-bodyCh: if !v { log.Println("BODY port is closed. Interrupting execution") exitCh <- syscall.SIGTERM break } else { total++ } case v := <-respCh: if !v { log.Println("RESP port is closed. Interrupting execution") exitCh <- syscall.SIGTERM break } else { total++ } case v := <-errCh: if !v { log.Println("ERR port is closed. Interrupting execution") exitCh <- syscall.SIGTERM break } else { total++ } } if total >= num && waitCh != nil { waitCh <- true } } }(ports) log.Println("Waiting for port connections to establish... ") select { case <-waitCh: log.Println("Ports connected") waitCh = nil case <-time.Tick(30 * time.Second): log.Println("Timeout: port connections were not established within provided interval") exitCh <- syscall.SIGTERM return } // This is obviously dangerous but we need it to deal with our custom CA's tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } client := &http.Client{Transport: tr} client.Timeout = 30 * time.Second // Main loop var ( ip [][]byte clientOptions *httputils.HTTPClientOptions request *http.Request ) log.Println("Started") for { ip, err = reqPort.RecvMessageBytes(zmq.DONTWAIT) if err != nil { select { case <-reqExitCh: log.Println("REQ port is closed. Interrupting execution") exitCh <- syscall.SIGTERM break default: // IN port is still open } time.Sleep(2 * time.Second) continue } if !runtime.IsValidIP(ip) { log.Println("Invalid IP:", ip) continue } err = json.Unmarshal(ip[1], &clientOptions) if err != nil { log.Println("ERROR: failed to unmarshal request options:", err.Error()) continue } if clientOptions == nil { log.Println("ERROR: received nil request options") continue } if clientOptions.Form != nil { request, err = http.NewRequest(clientOptions.Method, clientOptions.URL, strings.NewReader(clientOptions.Form.Encode())) } else { request, err = http.NewRequest(clientOptions.Method, clientOptions.URL, nil) } utils.AssertError(err) if clientOptions.ContentType != "" { request.Header.Add("Content-Type", clientOptions.ContentType) } for k, v := range clientOptions.Headers { request.Header.Add(k, v[0]) } response, err := client.Do(request) if err != nil { log.Printf("ERROR performing HTTP %s %s: %s", request.Method, request.URL, err.Error()) if errPort != nil { errPort.SendMessageDontwait(runtime.NewPacket([]byte(err.Error()))) } clientOptions = nil continue } resp, err := httputils.Response2Response(response) if err != nil { log.Printf("ERROR converting response to reply: %s", err.Error()) if errPort != nil { errPort.SendMessageDontwait(runtime.NewPacket([]byte(err.Error()))) } clientOptions = nil continue } ip, err = httputils.Response2IP(resp) if err != nil { log.Printf("ERROR converting reply to IP: %s", err.Error()) if errPort != nil { errPort.SendMessageDontwait(runtime.NewPacket([]byte(err.Error()))) } clientOptions = nil continue } if respPort != nil { respPort.SendMessage(ip) } if bodyPort != nil { bodyPort.SendMessage(runtime.NewPacket(resp.Body)) } select { case <-reqCh: log.Println("REQ port is closed. Interrupting execution") exitCh <- syscall.SIGTERM break default: // file port is still open } clientOptions = nil continue } }
func main() { flag.Parse() if *jsonFlag { doc, _ := registryEntry.JSON() fmt.Println(string(doc)) os.Exit(0) } log.SetFlags(0) if *debug { log.SetOutput(os.Stdout) } else { log.SetOutput(ioutil.Discard) } validateArgs() openPorts() defer closePorts() poller.Add(requestPort, zmq.POLLIN) exitCh := utils.HandleInterruption() err = runtime.SetupShutdownByDisconnect(requestPort, "http-router.in", exitCh) utils.AssertError(err) // Main loop var ( index int = -1 outputIndex int = -1 params url.Values ip [][]byte pLength int = len(pollItems) router *Router = NewRouter() ) for { // Poll sockets log.Println("Polling sockets...") sockets, err := poller.Poll(-1) if err != nil { log.Println("Error polling ports:", err.Error()) os.Exit(1) } for index, s := range sockets { ip, err = s.Socket.RecvMessageBytes(0) if err != nil { log.Printf("Failed to receive data. Error: %s", err.Error()) continue } if !runtime.IsValidIP(ip) { log.Println("Received invalid IP") continue } } // Pattern arrived if index < pLength-1 { // Close pattern socket port = pollItems[index].Socket port.Close() // Resolve corresponding output socket index outputIndex = -1 for i, s := range patternPorts { if s == port { outputIndex = i } } if outputIndex == -1 { log.Printf("Failed to resolve output socket index") continue } // Remove closed socket from polling items pollItems = append(pollItems[:index], pollItems[index+1:]...) pLength -= 1 // Add pattern to router parts := strings.Split(string(ip[1]), " ") method := strings.ToUpper(strings.TrimSpace(parts[0])) pattern := strings.TrimSpace(parts[1]) switch method { case "GET": router.Get(pattern, outputIndex) case "POST": router.Post(pattern, outputIndex) case "PUT": router.Put(pattern, outputIndex) case "DELETE": router.Del(pattern, outputIndex) case "HEAD": router.Head(pattern, outputIndex) case "OPTIONS": router.Options(pattern, outputIndex) default: log.Printf("Unsupported HTTP method %s in pattern %s", method, pattern) } continue } // Request arrive req, err := httputils.IP2Request(ip) if err != nil { log.Printf("Failed to convert IP to request. Error: %s", err.Error()) continue } outputIndex, params = router.Route(req.Method, req.URI) log.Printf("Output index for %s %s: %v (params=%#v)", req.Method, req.URI, outputIndex, params) switch outputIndex { case NotFound: log.Println("Sending Not Found response to FAIL output") resp := &httputils.HTTPResponse{ Id: req.Id, StatusCode: http.StatusNotFound, } ip, _ = httputils.Response2IP(resp) failPort.SendMultipart(ip, 0) case MethodNotAllowed: log.Println("Sending Method Not Allowed response to FAIL output") resp := &httputils.HTTPResponse{ Id: req.Id, StatusCode: http.StatusMethodNotAllowed, } ip, _ = httputils.Response2IP(resp) failPort.SendMultipart(ip, 0) default: for k, values := range params { req.Form[k] = values } ip, _ = httputils.Request2IP(req) successPorts[outputIndex].SendMultipart(ip, 0) } index = -1 } }