Пример #1
0
//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
}
Пример #2
0
//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
		}
	}
}