//export ListenAndServe func ListenAndServe(addr_ C.SEXP, handler_ C.SEXP, rho_ C.SEXP) C.SEXP { if C.TYPEOF(addr_) != C.STRSXP { fmt.Printf("addr is not a string (STRXSP; instead it is: %d)! addr argument to ListenAndServe() must be a string of form 'ip:port'\n", C.TYPEOF(addr_)) return C.R_NilValue } //msglen := 0 if 0 == int(C.isFunction(handler_)) { // 0 is false C.ReportErrorToR_NoReturn(C.CString("‘handler’ must be a function")) return C.R_NilValue } if rho_ != nil && rho_ != C.R_NilValue { if 0 == int(C.isEnvironment(rho_)) { // 0 is false C.ReportErrorToR_NoReturn(C.CString("‘rho’ should be an environment")) return C.R_NilValue } } caddr := C.R_CHAR(C.STRING_ELT(addr_, 0)) addr := C.GoString(caddr) fmt.Printf("ListenAndServe listening on address '%s'...\n", addr) webSockHandler := func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.Error(w, "Not found", 404) return } if r.Method != "GET" { http.Error(w, "Method not allowed, only GET allowed.", 405) return } c, err := upgrader.Upgrade(w, r, nil) if err != nil { fmt.Print("websocket handler upgrade error:", err) return } defer c.Close() mt, message, err := c.ReadMessage() if err != nil { fmt.Println("read error: ", err) return } // make the call, and get a response msglen := len(message) rawmsg := C.allocVector(C.RAWSXP, C.R_xlen_t(msglen)) C.Rf_protect(rawmsg) C.memcpy(unsafe.Pointer(C.RAW(rawmsg)), unsafe.Pointer(&message[0]), C.size_t(msglen)) // put msg into env that handler_ is called with. C.defineVar(C.install(C.CString("msg")), rawmsg, rho_) R_serialize_fun = C.findVar(C.install(C.CString("serialize")), C.R_GlobalEnv) // todo: callbacks to R functions here not working. don't really need them if R always acts as a client instead. // evaluate C.PrintToR(C.CString("listenAndServe: stuffed msg into env rho_.\n")) //R_fcall := C.lang3(handler_, rawmsg, C.R_NilValue) R_fcall := C.lang3(R_serialize_fun, rawmsg, C.R_NilValue) C.Rf_protect(R_fcall) C.PrintToR(C.CString("listenAndServe: got msg, just prior to eval.\n")) evalres := C.eval(R_fcall, rho_) C.Rf_protect(evalres) C.PrintToR(C.CString("listenAndServe: after eval.\n")) /* var s, t C.SEXP s = C.allocList(3) t = s C.Rf_protect(t) C.SetTypeToLANGSXP(&s) //C.SETCAR(t, R_fcall) C.SETCAR(t, handler_) t = C.CDR(t) C.SETCAR(t, rawmsg) evalres := C.eval(s, rho_) C.Rf_protect(evalres) */ C.PrintToR(C.CString("nnListenAndServe: done with eval.\n")) if C.TYPEOF(evalres) != C.RAWSXP { fmt.Printf("rats! handler result was not RAWSXP raw bytes!\n") } else { //fmt.Printf("recv: %s\n", message) err = c.WriteMessage(mt, message) if err != nil { fmt.Println("write error: ", err) } } C.Rf_unprotect(3) } // end handler func http.HandleFunc("/", webSockHandler) err := http.ListenAndServe(addr, nil) if err != nil { fmt.Println("ListenAndServe: ", err) } return C.R_NilValue }
//export ListenAndServe // // ListenAndServe is the server part that expects calls from client // in the form of RmqWebsocketCall() invocations. // The underlying websocket library is the battle tested // https://github.com/gorilla/websocket library from the // Gorilla Web toolkit. http://www.gorillatoolkit.org/ // // addr_ is a string in "ip:port" format. The server // will bind this address and port on the local host. // // handler_ is an R function that takes a single argument. // It will be called back each time the server receives // an incoming message. The returned value of handler // becomes the reply to the client. // // rho_ is an R environment in which the handler_ callback // will occur. The user-level wrapper rmq.server() provides // a new environment for every call back by default, so // most users won't need to worry about rho_. // // Return value: this is always R_NilValue. // // Semantics: ListenAndServe() will start a new // webserver everytime it is called. If it exits // due to a call into R_CheckUserInterrupt() // or Rf_error(), then a background watchdog goroutine // will notice the lack of heartbeating after 300ms, // and will immediately shutdown the listening // websocket server goroutine. Hence cleanup // is fairly automatic. // // Signal handling: // // SIGINT (ctrl-c) is noted by R, and since we // regularly call R_CheckUserInterrupt(), the // user can stop the server by pressing ctrl-c // at the R-console. The go-runtime, as embedded // in the c-shared library, is not accustomed to being // embedded yet, and so its (system) signal handling // facilities (e.g. signal.Notify) should *not* be // used. We go to great pains to actually preserve // the signal handling that R sets up and expects, // as allowing the go runtime to see any signals just // creates heartache and crashes. // func ListenAndServe(addr_ C.SEXP, handler_ C.SEXP, rho_ C.SEXP) C.SEXP { addr, err := getAddr(addr_) if err != nil { C.ReportErrorToR_NoReturn(C.CString(err.Error())) return C.R_NilValue } if 0 == int(C.isFunction(handler_)) { // 0 is false C.ReportErrorToR_NoReturn(C.CString("‘handler’ must be a function")) return C.R_NilValue } if rho_ != nil && rho_ != C.R_NilValue { if 0 == int(C.isEnvironment(rho_)) { // 0 is false C.ReportErrorToR_NoReturn(C.CString("‘rho’ should be an environment")) return C.R_NilValue } } fmt.Printf("ListenAndServe listening on address '%s'...\n", addr) // Motivation: One problem when acting as a web server is that // webSockHandler will be run on a separate goroutine and this will surely // be a separate thread--distinct from the R callback thread. This is a // problem because if we call back into R from the goroutine thread // instead of R's thread, R will see the small stack and freak out. // // So: we'll use a channel to send the request to the main R thread // for call back into R. The *[]byte passed on these channels represent // msgpack serialized R objects. requestToRCh := make(chan *[]byte) replyFromRCh := make(chan *[]byte) reqStopCh := make(chan bool) doneCh := make(chan bool) var lastControlHeartbeatTimeNano int64 beatHeart := func() { now := int64(time.Now().UnixNano()) atomic.StoreInt64(&lastControlHeartbeatTimeNano, now) } webSockHandler := func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.Error(w, "Not found", 404) return } if r.Method != "GET" { http.Error(w, "Method not allowed, only GET allowed.", 405) return } c, err := upgrader.Upgrade(w, r, nil) if err != nil { msg := fmt.Sprintf("server webSockHandler() handler saw "+ "websocket upgrader.Upgrade() error: '%s'", err) fmt.Printf("%s\n", msg) http.Error(w, msg, 500) return } defer c.Close() _, message, err := c.ReadMessage() if err != nil { msg := fmt.Sprintf("server webSockHandler() handler saw "+ "websocket ReadMessage() error: '%s'", err) fmt.Printf("%s\n", msg) http.Error(w, msg, 500) return } requestToRCh <- &message reply := <-replyFromRCh err = c.WriteMessage(websocket.BinaryMessage, *reply) if err != nil { msg := fmt.Sprintf("server webSockHandler() handler saw "+ "websocket WriteMessage() error: '%s'", err) fmt.Printf("%s\n", msg) http.Error(w, msg, 500) return } } // end webSockHandler // start a new server, to avoid registration issues with // the default http library mux/server which may be in use // already for other purposes. mux := http.NewServeMux() mux.HandleFunc("/", webSockHandler) server := NewWebServer(addr.String(), mux) server.Start() // This watchdog will shut the webserver down // if lastControlHeartbeatTimeNano goes // too long without an update. This will // happen when C.R_CheckUserInterrupt() // doesn't return below in the main control loop. beatHeart() go func() { for { select { case <-time.After(time.Millisecond * 100): last := atomic.LoadInt64(&lastControlHeartbeatTimeNano) lastTm := time.Unix(0, last) deadline := lastTm.Add(300 * time.Millisecond) now := time.Now() if now.After(deadline) { VPrintf("\n web-server watchdog: no heartbeat "+ "after %v, shutting down.\n", now.Sub(lastTm)) server.Stop() return } } } }() // This is the main control routine that lives on the main R thread. // All callbacks into R must come from this thread, as it is a // C thread with a big stack. Go routines have tiny stacks and // R detects this and crashes if you try to call back from one of them. for { select { case <-time.After(time.Millisecond * 100): // // Our heartbeat logic: // // R_CheckUserInterrupt() will check if Ctrl-C // was pressed by user and R would like us to stop. // (R's SIGINT signal handler is installed in our // package init() routine at the top of this file.) // // Note that R_CheckUserInterrupt() may not return! // So, Q: how will the server know to cancel/cleanup? // A: we'll have the other goroutines check the following // timestamp. If it is too out of date, then they'll // know that they should cleanup. beatHeart() C.R_CheckUserInterrupt() case msgpackRequest := <-requestToRCh: rRequest := decodeMsgpackToR(*msgpackRequest) C.Rf_protect(rRequest) // Call into the R handler_ function, and get its reply. R_fcall := C.lang2(handler_, rRequest) C.Rf_protect(R_fcall) if Verbose { C.PrintToR(C.CString("listenAndServe: got msg, just prior to eval.\n")) } evalres := C.eval(R_fcall, rho_) C.Rf_protect(evalres) if Verbose { C.PrintToR(C.CString("listenAndServe: after eval.\n")) } // send back the reply, first converting to msgpack reply := encodeRIntoMsgpack(evalres) C.Rf_unprotect(3) replyFromRCh <- &reply case <-reqStopCh: // not sure who should close(reqStopCh). At the moment it isn't used. close(doneCh) return C.R_NilValue } } }