From 0e04beadf0728c4549b120f8a5d65a242f546675 Mon Sep 17 00:00:00 2001 From: SpencerPark Date: Fri, 25 Aug 2017 21:56:25 -0400 Subject: [PATCH 1/5] Wrap common message construction patterns to clean up the jupyter communication. --- messages.go | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/messages.go b/messages.go index 783519e..3cbe054 100644 --- a/messages.go +++ b/messages.go @@ -6,7 +6,7 @@ import ( "encoding/hex" "encoding/json" - uuid "github.com/nu7hatch/gouuid" + "github.com/nu7hatch/gouuid" zmq "github.com/pebbe/zmq4" ) @@ -182,3 +182,29 @@ func NewMsg(msgType string, parent ComposedMsg) (ComposedMsg, error) { return msg, nil } + +// Publish creates a new ComposedMsg and sends it back to the return identities over the +// IOPub channel +func (receipt *msgReceipt) Publish(msgType string, content interface{}) error { + msg, err := NewMsg(msgType, receipt.Msg) + + if err != nil { + return err + } + + msg.Content = content + return receipt.SendResponse(receipt.Sockets.IOPubSocket, msg) +} + +// Reply creates a new ComposedMsg and sends it back to the return identities over the +// Shell channel +func (receipt *msgReceipt) Reply(msgType string, content interface{}) error { + msg, err := NewMsg(msgType, receipt.Msg) + + if err != nil { + return err + } + + msg.Content = content + return receipt.SendResponse(receipt.Sockets.ShellSocket, msg) +} From 825aefddc26a58f8fac07edaa11736345bdd7203 Mon Sep 17 00:00:00 2001 From: SpencerPark Date: Fri, 25 Aug 2017 23:45:04 -0400 Subject: [PATCH 2/5] Update messages to the 5.0 spec and encapsulate. --- kernel.go | 149 +++++++++++++++++++++++++--------------------------- main.go | 2 + messages.go | 130 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 204 insertions(+), 77 deletions(-) diff --git a/kernel.go b/kernel.go index 0751c68..d558d5e 100644 --- a/kernel.go +++ b/kernel.go @@ -9,6 +9,7 @@ import ( "io/ioutil" "log" "os" + "runtime" "strings" "github.com/cosmos72/gomacro/base" @@ -43,18 +44,38 @@ type SocketGroup struct { Key []byte } -// kernelInfo holds information about the igo kernel, for -// kernel_info_reply messages. -type kernelInfo struct { - ProtocolVersion []int `json:"protocol_version"` - Language string `json:"language"` -} - // kernelStatus holds a kernel state, for status broadcast messages. type kernelStatus struct { ExecutionState string `json:"execution_state"` } +// KernelLanguageInfo holds information about the language that this kernel executes code in +type kernelLanguageInfo struct { + Name string `json:"name"` + Version string `json:"version"` + MIMEType string `json:"mimetype"` + FileExtension string `json:"file_extension"` + PygmentsLexer string `json:"pygments_lexer"` + CodeMirrorMode string `json:"codemirror_mode"` + NBConvertExporter string `json:"nbconvert_exporter"` +} + +// HelpLink stores data to be displayed in the help menu of the notebook +type helpLink struct { + Text string `json:"text"` + URL string `json:"url"` +} + +// KernelInfo holds information about the igo kernel, for kernel_info_reply messages. +type kernelInfo struct { + ProtocolVersion string `json:"protocol_version"` + Implementation string `json:"implementation"` + ImplementationVersion string `json:"implementation_version"` + LanguageInfo kernelLanguageInfo `json:"language_info"` + Banner string `json:"banner"` + HelpLinks []helpLink `json:"help_links"` +} + // shutdownReply encodes a boolean indication of stutdown/restart type shutdownReply struct { Restart bool `json:"restart"` @@ -120,11 +141,11 @@ func runKernel(connectionFile string) { handleShellMsg(ir, msgReceipt{msg, ids, sockets}) - // TODO Handle stdin socket. + // TODO Handle stdin socket. case sockets.StdinSocket: sockets.StdinSocket.RecvMessageBytes(0) - // Handle control messages. + // Handle control messages. case sockets.ControlSocket: msgParts, err = sockets.ControlSocket.RecvMessageBytes(0) if err != nil { @@ -210,31 +231,29 @@ func handleShellMsg(ir *classic.Interp, receipt msgReceipt) { // sendKernelInfo sends a kernel_info_reply message. func sendKernelInfo(receipt msgReceipt) error { - reply, err := NewMsg("kernel_info_reply", receipt.Msg) - if err != nil { - return err - } - - reply.Content = kernelInfo{[]int{4, 0}, "go"} - if err := receipt.SendResponse(receipt.Sockets.ShellSocket, reply); err != nil { - return err - } - - return nil + return receipt.Reply("kernel_info_reply", + kernelInfo{ + ProtocolVersion: "5.0", + Implementation: "gophernotes", + ImplementationVersion: Version, + Banner: fmt.Sprintf("Go kernel: gophernotes - v%s", Version), + LanguageInfo: kernelLanguageInfo{ + Name: "go", + Version: runtime.Version(), + FileExtension: ".go", + }, + HelpLinks: []helpLink{ + {Text: "Go", URL: "https://golang.org/"}, + {Text: "gophernotes", URL: "https://github.com/gopherdata/gophernotes"}, + }, + }, + ) } // handleExecuteRequest runs code from an execute_request method, // and sends the various reply messages. func handleExecuteRequest(ir *classic.Interp, receipt msgReceipt) error { - - // Prepare the reply message. - reply, err := NewMsg("execute_reply", receipt.Msg) - if err != nil { - return err - } - - content := make(map[string]interface{}) - + // Extract the data from the request reqcontent := receipt.Msg.Content.(map[string]interface{}) code := reqcontent["code"].(string) in := bufio.NewReader(strings.NewReader(code)) @@ -244,8 +263,18 @@ func handleExecuteRequest(ir *classic.Interp, receipt msgReceipt) error { ExecCounter++ } + // Prepare the map that will hold the reply content + content := make(map[string]interface{}) content["execution_count"] = ExecCounter + // Tell the front-end that the kernel is working and when finished notify the + // front-end that the kernel is idle again + receipt.PublishKernelBusy() + defer receipt.PublishKernelIdle() + + // Tell the front-end what the kernel is about to execute + receipt.PublishExecutionInput(ExecCounter, code) + // Redirect the standard out from the REPL. oldStdout := os.Stdout rOut, wOut, err := os.Pipe() @@ -306,25 +335,15 @@ func handleExecuteRequest(ir *classic.Interp, receipt msgReceipt) error { wErr.Close() stdErr := <-outStderr + // TODO write stdout and stderr to streams rather than publishing as results + if len(val) > 0 { content["status"] = "ok" - content["payload"] = make([]map[string]interface{}, 0) - content["user_variables"] = make(map[string]string) content["user_expressions"] = make(map[string]string) - if !silent { - var outContent OutputMsg - - out, err := NewMsg("execute_result", receipt.Msg) - if err != nil { - return err - } - outContent.Execcount = ExecCounter - outContent.Data = make(map[string]string) - outContent.Data["text/plain"] = val - outContent.Metadata = make(map[string]interface{}) - out.Content = outContent - receipt.SendResponse(receipt.Sockets.IOPubSocket, out) + if !silent { + // Publish the result of the execution + receipt.PublishExecutionResult(ExecCounter, val) } } @@ -334,51 +353,29 @@ func handleExecuteRequest(ir *classic.Interp, receipt msgReceipt) error { content["evalue"] = stdErr content["traceback"] = nil - errormsg, err := NewMsg("pyerr", receipt.Msg) - if err != nil { - return err - } - - errormsg.Content = ErrMsg{"Error", stdErr, []string{stdErr}} - receipt.SendResponse(receipt.Sockets.IOPubSocket, errormsg) + receipt.PublishExecutionError(stdErr, stdErr) } // Send the output back to the notebook. - reply.Content = content - - if err := receipt.SendResponse(receipt.Sockets.ShellSocket, reply); err != nil { - return err - } - - idle, err := NewMsg("status", receipt.Msg) - if err != nil { - return err - } - - idle.Content = kernelStatus{"idle"} - - if err := receipt.SendResponse(receipt.Sockets.IOPubSocket, idle); err != nil { - return err - } - - return nil + return receipt.Reply("execute_reply", content) } // handleShutdownRequest sends a "shutdown" message func handleShutdownRequest(receipt msgReceipt) { - reply, err := NewMsg("shutdown_reply", receipt.Msg) - if err != nil { - log.Fatal(err) - } - content := receipt.Msg.Content.(map[string]interface{}) restart := content["restart"].(bool) - reply.Content = shutdownReply{restart} - if err := receipt.SendResponse(receipt.Sockets.ShellSocket, reply); err != nil { + err := receipt.Reply("shutdown_reply", + shutdownReply{ + Restart: restart, + }, + ) + + if err != nil { log.Fatal(err) } + log.Println("Shutting down in response to shutdown_request") os.Exit(0) } diff --git a/main.go b/main.go index 8606328..3f34d1d 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,8 @@ import ( "log" ) +const Version string = "1.0.0" + func main() { // Parse the connection file. diff --git a/messages.go b/messages.go index 3cbe054..e31ca37 100644 --- a/messages.go +++ b/messages.go @@ -71,7 +71,7 @@ func WireMsgToComposedMsg(msgparts [][]byte, signkey []byte) (ComposedMsg, [][]b var msg ComposedMsg if len(signkey) != 0 { mac := hmac.New(sha256.New, signkey) - for _, msgpart := range msgparts[i+2 : i+6] { + for _, msgpart := range msgparts[i+2: i+6] { mac.Write(msgpart) } signature := make([]byte, hex.DecodedLen(len(msgparts[i+1]))) @@ -208,3 +208,131 @@ func (receipt *msgReceipt) Reply(msgType string, content interface{}) error { msg.Content = content return receipt.SendResponse(receipt.Sockets.ShellSocket, msg) } + +// MIMEDataBundle holds data that can be presented in multiple formats. The keys are MIME types +// and the values are the data formatted with respect to it's MIME type. All bundle should contain +// at least a "text/plain" representation with a string value. +type MIMEDataBundle map[string]interface{} + +// NewTextMIMEDataBundle creates a MIMEDataBundle that only contains a text representation described +// the the parameter 'value' +func NewTextMIMEDataBundle(value string) MIMEDataBundle { + return MIMEDataBundle{ + "text/plain": value, + } +} + +// KernelStatus holds a kernel execution state, for status broadcast messages. +type KernelStatus struct { + ExecutionState string `json:"execution_state"` +} + +// PublishKernelStarting publishes a status message notifying front-ends that the kernel is +// starting up. +func (receipt *msgReceipt) PublishKernelStarting() { + receipt.Publish("status", + KernelStatus{ + ExecutionState: "starting", + }, + ) +} + +// PublishKernelBusy publishes a status message notifying front-ends that the kernel is +// doing work. +func (receipt *msgReceipt) PublishKernelBusy() { + receipt.Publish("status", + KernelStatus{ + ExecutionState: "busy", + }, + ) +} + +// PublishKernelIdle publishes a status message notifying front-ends that the kernel is +// free. +func (receipt *msgReceipt) PublishKernelIdle() { + receipt.Publish("status", + KernelStatus{ + ExecutionState: "idle", + }, + ) +} + +// ExecuteInput holds the source code being executed and the execution counter value +// associated with source being run. +type ExecuteInput struct { + ExecCount int `json:"execution_count"` + Code string `json:"code"` +} + +// PublishExecutionInput publishes a status message notifying front-ends of what code is +// currently being executed. +func (receipt *msgReceipt) PublishExecutionInput(execCount int, code string) { + receipt.Publish("execute_input", + ExecuteInput{ + ExecCount: execCount, + Code: code, + }, + ) +} + +// ExecuteResult holds the output to the 'ExecCount'th code execution. +type ExecuteResult struct { + ExecCount int `json:"execution_count"` + Data MIMEDataBundle `json:"data"` + Metadata MIMEDataBundle `json:"metadata"` +} + +// PublishExecuteResult publishes the result of the 'execCount'th execution as a string. +func (receipt *msgReceipt) PublishExecutionResult(execCount int, output string) { + receipt.Publish("execute_result", + ExecuteResult{ + ExecCount: execCount, + Data: NewTextMIMEDataBundle(output), + Metadata: make(MIMEDataBundle), + }, + ) +} + +// ExecuteError holds data describing an error encountered during execution. +type ExecuteError struct { + Name string `json:"ename"` + Value string `json:"evalue"` + Trace []string `json:"traceback"` +} + +// PublishExecuteResult publishes a serialized error that was encountered during execution. +func (receipt *msgReceipt) PublishExecutionError(err string, trace string) { + receipt.Publish("error", + ExecuteError{ + Name: "ERROR", + Value: err, + Trace: []string{trace}, + }, + ) +} + +// WriteStreamData holds data to be written to a stream (stdout, stderr) +type WriteStreamData struct { + Stream string `json:"name"` + Data string `json:"text"` +} + +// PublishWriteStdOut publishes the data string to the front-end's stdout +func (receipt *msgReceipt) PublishWriteStdOut(data string) { + receipt.Publish("stream", + WriteStreamData{ + Stream: "stdout", + Data: data, + }, + ) +} + +// PublishWriteStdErr publishes the data string to the front-end's stderr +func (receipt *msgReceipt) PublishWriteStdErr(data string) { + receipt.Publish("stream", + WriteStreamData{ + Stream: "stderr", + Data: data, + }, + ) +} From e287c995dd1407b1aee794ffa313c8a651f35217 Mon Sep 17 00:00:00 2001 From: SpencerPark Date: Thu, 7 Sep 2017 20:51:43 -0400 Subject: [PATCH 3/5] Handle errors from publish messages, clean up protocol wrappers, and fix godoc comment formatting. --- kernel.go | 51 +++++++++++-------- messages.go | 137 ++++++++++++++++++++-------------------------------- 2 files changed, 83 insertions(+), 105 deletions(-) diff --git a/kernel.go b/kernel.go index d558d5e..0c86e33 100644 --- a/kernel.go +++ b/kernel.go @@ -49,7 +49,7 @@ type kernelStatus struct { ExecutionState string `json:"execution_state"` } -// KernelLanguageInfo holds information about the language that this kernel executes code in +// KernelLanguageInfo holds information about the language that this kernel executes code in. type kernelLanguageInfo struct { Name string `json:"name"` Version string `json:"version"` @@ -60,7 +60,7 @@ type kernelLanguageInfo struct { NBConvertExporter string `json:"nbconvert_exporter"` } -// HelpLink stores data to be displayed in the help menu of the notebook +// HelpLink stores data to be displayed in the help menu of the notebook. type helpLink struct { Text string `json:"text"` URL string `json:"url"` @@ -76,7 +76,7 @@ type kernelInfo struct { HelpLinks []helpLink `json:"help_links"` } -// shutdownReply encodes a boolean indication of stutdown/restart +// shutdownReply encodes a boolean indication of stutdown/restart. type shutdownReply struct { Restart bool `json:"restart"` } @@ -263,17 +263,25 @@ func handleExecuteRequest(ir *classic.Interp, receipt msgReceipt) error { ExecCounter++ } - // Prepare the map that will hold the reply content + // Prepare the map that will hold the reply content. content := make(map[string]interface{}) content["execution_count"] = ExecCounter // Tell the front-end that the kernel is working and when finished notify the - // front-end that the kernel is idle again - receipt.PublishKernelBusy() - defer receipt.PublishKernelIdle() + // front-end that the kernel is idle again. + if err := receipt.PublishKernelStatus(KernelBusy); err != nil { + log.Printf("Error publishing kernel status 'busy': %v\n", err) + } + defer func() { + if err := receipt.PublishKernelStatus(KernelIdle); err != nil { + log.Printf("Error publishing kernel status 'idle': %v\n", err) + } + }() - // Tell the front-end what the kernel is about to execute - receipt.PublishExecutionInput(ExecCounter, code) + // Tell the front-end what the kernel is about to execute. + if err := receipt.PublishExecutionInput(ExecCounter, code); err != nil { + log.Printf("Error publishing execution input: %v\n", err) + } // Redirect the standard out from the REPL. oldStdout := os.Stdout @@ -295,7 +303,7 @@ func handleExecuteRequest(ir *classic.Interp, receipt msgReceipt) error { env.Options &^= base.OptShowPrompt env.Line = 0 - // Perform the first iteration manually, to collect comments + // Perform the first iteration manually, to collect comments. var comments string str, firstToken := env.ReadMultiline(in, base.ReadOptCollectAllComments) if firstToken >= 0 { @@ -342,8 +350,10 @@ func handleExecuteRequest(ir *classic.Interp, receipt msgReceipt) error { content["user_expressions"] = make(map[string]string) if !silent { - // Publish the result of the execution - receipt.PublishExecutionResult(ExecCounter, val) + // Publish the result of the execution. + if err := receipt.PublishExecutionResult(ExecCounter, val); err != nil { + log.Printf("Error publishing execution result: %v\n", err) + } } } @@ -353,29 +363,28 @@ func handleExecuteRequest(ir *classic.Interp, receipt msgReceipt) error { content["evalue"] = stdErr content["traceback"] = nil - receipt.PublishExecutionError(stdErr, stdErr) + if err := receipt.PublishExecutionError(stdErr, []string{stdErr}); err != nil { + log.Printf("Error publishing execution error: %v\n", err) + } } // Send the output back to the notebook. return receipt.Reply("execute_reply", content) } -// handleShutdownRequest sends a "shutdown" message +// handleShutdownRequest sends a "shutdown" message. func handleShutdownRequest(receipt msgReceipt) { content := receipt.Msg.Content.(map[string]interface{}) restart := content["restart"].(bool) - err := receipt.Reply("shutdown_reply", - shutdownReply{ - Restart: restart, - }, - ) + reply := shutdownReply{ + Restart: restart, + } - if err != nil { + if err := receipt.Reply("shutdown_reply", reply); err != nil { log.Fatal(err) } - log.Println("Shutting down in response to shutdown_request") os.Exit(0) } diff --git a/messages.go b/messages.go index e31ca37..3185ef5 100644 --- a/messages.go +++ b/messages.go @@ -71,7 +71,7 @@ func WireMsgToComposedMsg(msgparts [][]byte, signkey []byte) (ComposedMsg, [][]b var msg ComposedMsg if len(signkey) != 0 { mac := hmac.New(sha256.New, signkey) - for _, msgpart := range msgparts[i+2: i+6] { + for _, msgpart := range msgparts[i+2 : i+6] { mac.Write(msgpart) } signature := make([]byte, hex.DecodedLen(len(msgparts[i+1]))) @@ -184,7 +184,7 @@ func NewMsg(msgType string, parent ComposedMsg) (ComposedMsg, error) { } // Publish creates a new ComposedMsg and sends it back to the return identities over the -// IOPub channel +// IOPub channel. func (receipt *msgReceipt) Publish(msgType string, content interface{}) error { msg, err := NewMsg(msgType, receipt.Msg) @@ -197,7 +197,7 @@ func (receipt *msgReceipt) Publish(msgType string, content interface{}) error { } // Reply creates a new ComposedMsg and sends it back to the return identities over the -// Shell channel +// Shell channel. func (receipt *msgReceipt) Reply(msgType string, content interface{}) error { msg, err := NewMsg(msgType, receipt.Msg) @@ -215,77 +215,54 @@ func (receipt *msgReceipt) Reply(msgType string, content interface{}) error { type MIMEDataBundle map[string]interface{} // NewTextMIMEDataBundle creates a MIMEDataBundle that only contains a text representation described -// the the parameter 'value' +// the the parameter 'value'. func NewTextMIMEDataBundle(value string) MIMEDataBundle { return MIMEDataBundle{ "text/plain": value, } } -// KernelStatus holds a kernel execution state, for status broadcast messages. -type KernelStatus struct { - ExecutionState string `json:"execution_state"` -} - -// PublishKernelStarting publishes a status message notifying front-ends that the kernel is -// starting up. -func (receipt *msgReceipt) PublishKernelStarting() { - receipt.Publish("status", - KernelStatus{ - ExecutionState: "starting", - }, - ) -} +type KernelStatus string -// PublishKernelBusy publishes a status message notifying front-ends that the kernel is -// doing work. -func (receipt *msgReceipt) PublishKernelBusy() { - receipt.Publish("status", - KernelStatus{ - ExecutionState: "busy", - }, - ) -} +const ( + KernelStarting KernelStatus = "starting" + KernelBusy = "busy" + KernelIdle = "idle" +) -// PublishKernelIdle publishes a status message notifying front-ends that the kernel is -// free. -func (receipt *msgReceipt) PublishKernelIdle() { - receipt.Publish("status", - KernelStatus{ - ExecutionState: "idle", +// PublishKernelStatus publishes a status message notifying front-ends of the state the kernel is in. +func (receipt *msgReceipt) PublishKernelStatus(status KernelStatus) error { + return receipt.Publish("status", + struct { + ExecutionState KernelStatus `json:"execution_state"` + }{ + ExecutionState: status, }, ) } -// ExecuteInput holds the source code being executed and the execution counter value -// associated with source being run. -type ExecuteInput struct { - ExecCount int `json:"execution_count"` - Code string `json:"code"` -} - // PublishExecutionInput publishes a status message notifying front-ends of what code is // currently being executed. -func (receipt *msgReceipt) PublishExecutionInput(execCount int, code string) { - receipt.Publish("execute_input", - ExecuteInput{ +func (receipt *msgReceipt) PublishExecutionInput(execCount int, code string) error { + return receipt.Publish("execute_input", + struct { + ExecCount int `json:"execution_count"` + Code string `json:"code"` + }{ ExecCount: execCount, Code: code, }, ) } -// ExecuteResult holds the output to the 'ExecCount'th code execution. -type ExecuteResult struct { - ExecCount int `json:"execution_count"` - Data MIMEDataBundle `json:"data"` - Metadata MIMEDataBundle `json:"metadata"` -} - -// PublishExecuteResult publishes the result of the 'execCount'th execution as a string. -func (receipt *msgReceipt) PublishExecutionResult(execCount int, output string) { - receipt.Publish("execute_result", - ExecuteResult{ +// PublishExecuteResult publishes the result of the `execCount` execution as a string. +func (receipt *msgReceipt) PublishExecutionResult(execCount int, output string) error { + return receipt.Publish("execute_result", + struct { + ExecCount int `json:"execution_count"` + Data MIMEDataBundle `json:"data"` + Metadata MIMEDataBundle `json:"metadata"` + }{ ExecCount: execCount, Data: NewTextMIMEDataBundle(output), Metadata: make(MIMEDataBundle), @@ -293,45 +270,37 @@ func (receipt *msgReceipt) PublishExecutionResult(execCount int, output string) ) } -// ExecuteError holds data describing an error encountered during execution. -type ExecuteError struct { - Name string `json:"ename"` - Value string `json:"evalue"` - Trace []string `json:"traceback"` -} - // PublishExecuteResult publishes a serialized error that was encountered during execution. -func (receipt *msgReceipt) PublishExecutionError(err string, trace string) { - receipt.Publish("error", - ExecuteError{ +func (receipt *msgReceipt) PublishExecutionError(err string, trace []string) error { + return receipt.Publish("error", + struct { + Name string `json:"ename"` + Value string `json:"evalue"` + Trace []string `json:"traceback"` + }{ Name: "ERROR", Value: err, - Trace: []string{trace}, + Trace: trace, }, ) } -// WriteStreamData holds data to be written to a stream (stdout, stderr) -type WriteStreamData struct { - Stream string `json:"name"` - Data string `json:"text"` -} +type Stream string -// PublishWriteStdOut publishes the data string to the front-end's stdout -func (receipt *msgReceipt) PublishWriteStdOut(data string) { - receipt.Publish("stream", - WriteStreamData{ - Stream: "stdout", - Data: data, - }, - ) -} +const ( + StreamStdout Stream = "stdout" + StreamStderr = "stderr" +) -// PublishWriteStdErr publishes the data string to the front-end's stderr -func (receipt *msgReceipt) PublishWriteStdErr(data string) { - receipt.Publish("stream", - WriteStreamData{ - Stream: "stderr", +// PublishWriteStream prints the data string to a stream on the front-end. This is +// either `StreamStdout` or `StreamStderr`. +func (receipt *msgReceipt) PublishWriteStream(stream Stream, data string) error { + return receipt.Publish("stream", + struct { + Stream Stream `json:"name"` + Data string `json:"text"` + }{ + Stream: stream, Data: data, }, ) From 1d4547ee7182762aa97bb308f16976084808a64c Mon Sep 17 00:00:00 2001 From: SpencerPark Date: Tue, 12 Sep 2017 10:18:28 -0400 Subject: [PATCH 4/5] Add 'date' and required 'version' field to the message header --- kernel.go | 2 +- main.go | 1 + messages.go | 13 +++++++++---- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/kernel.go b/kernel.go index 0c86e33..5fa8800 100644 --- a/kernel.go +++ b/kernel.go @@ -233,7 +233,7 @@ func handleShellMsg(ir *classic.Interp, receipt msgReceipt) { func sendKernelInfo(receipt msgReceipt) error { return receipt.Reply("kernel_info_reply", kernelInfo{ - ProtocolVersion: "5.0", + ProtocolVersion: ProtocolVersion, Implementation: "gophernotes", ImplementationVersion: Version, Banner: fmt.Sprintf("Go kernel: gophernotes - v%s", Version), diff --git a/main.go b/main.go index 3f34d1d..9ae36e2 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( ) const Version string = "1.0.0" +const ProtocolVersion string = "5.0" func main() { diff --git a/messages.go b/messages.go index 3185ef5..5e7fe9d 100644 --- a/messages.go +++ b/messages.go @@ -5,6 +5,7 @@ import ( "crypto/sha256" "encoding/hex" "encoding/json" + "time" "github.com/nu7hatch/gouuid" zmq "github.com/pebbe/zmq4" @@ -12,10 +13,12 @@ import ( // MsgHeader encodes header info for ZMQ messages. type MsgHeader struct { - MsgID string `json:"msg_id"` - Username string `json:"username"` - Session string `json:"session"` - MsgType string `json:"msg_type"` + MsgID string `json:"msg_id"` + Username string `json:"username"` + Session string `json:"session"` + MsgType string `json:"msg_type"` + ProtocolVersion string `json:"version"` + Timestamp string `json:"date"` } // ComposedMsg represents an entire message in a high-level structure. @@ -173,6 +176,8 @@ func NewMsg(msgType string, parent ComposedMsg) (ComposedMsg, error) { msg.Header.Session = parent.Header.Session msg.Header.Username = parent.Header.Username msg.Header.MsgType = msgType + msg.Header.ProtocolVersion = ProtocolVersion + msg.Header.Timestamp = time.Now().UTC().Format(time.RFC3339) u, err := uuid.NewV4() if err != nil { From f75e44fd9fbfd33e5e1a0c5d019ab71c75838d12 Mon Sep 17 00:00:00 2001 From: SpencerPark Date: Tue, 12 Sep 2017 11:12:59 -0400 Subject: [PATCH 5/5] Remove unused types/methods and clean up the messages interface. --- kernel.go | 15 ++++++----- messages.go | 76 ++++++++++++----------------------------------------- 2 files changed, 25 insertions(+), 66 deletions(-) diff --git a/kernel.go b/kernel.go index 5fa8800..5fe6de1 100644 --- a/kernel.go +++ b/kernel.go @@ -44,11 +44,6 @@ type SocketGroup struct { Key []byte } -// kernelStatus holds a kernel state, for status broadcast messages. -type kernelStatus struct { - ExecutionState string `json:"execution_state"` -} - // KernelLanguageInfo holds information about the language that this kernel executes code in. type kernelLanguageInfo struct { Name string `json:"name"` @@ -81,6 +76,12 @@ type shutdownReply struct { Restart bool `json:"restart"` } +const ( + kernelStarting = "starting" + kernelBusy = "busy" + kernelIdle = "idle" +) + // runKernel is the main entry point to start the kernel. func runKernel(connectionFile string) { @@ -269,11 +270,11 @@ func handleExecuteRequest(ir *classic.Interp, receipt msgReceipt) error { // Tell the front-end that the kernel is working and when finished notify the // front-end that the kernel is idle again. - if err := receipt.PublishKernelStatus(KernelBusy); err != nil { + if err := receipt.PublishKernelStatus(kernelBusy); err != nil { log.Printf("Error publishing kernel status 'busy': %v\n", err) } defer func() { - if err := receipt.PublishKernelStatus(KernelIdle); err != nil { + if err := receipt.PublishKernelStatus(kernelIdle); err != nil { log.Printf("Error publishing kernel status 'idle': %v\n", err) } }() diff --git a/messages.go b/messages.go index 5e7fe9d..49cbd68 100644 --- a/messages.go +++ b/messages.go @@ -37,19 +37,10 @@ type msgReceipt struct { Sockets SocketGroup } -// OutputMsg holds the data for a pyout message. -type OutputMsg struct { - Execcount int `json:"execution_count"` - Data map[string]string `json:"data"` - Metadata map[string]interface{} `json:"metadata"` -} - -// ErrMsg encodes the traceback of errors output to the notebook. -type ErrMsg struct { - EName string `json:"ename"` - EValue string `json:"evalue"` - Traceback []string `json:"traceback"` -} +// bundledMIMEData holds data that can be presented in multiple formats. The keys are MIME types +// and the values are the data formatted with respect to it's MIME type. All bundles should contain +// at least a "text/plain" representation with a string value. +type bundledMIMEData map[string]interface{} // InvalidSignatureError is returned when the signature on a received message does not // validate. @@ -214,32 +205,20 @@ func (receipt *msgReceipt) Reply(msgType string, content interface{}) error { return receipt.SendResponse(receipt.Sockets.ShellSocket, msg) } -// MIMEDataBundle holds data that can be presented in multiple formats. The keys are MIME types -// and the values are the data formatted with respect to it's MIME type. All bundle should contain -// at least a "text/plain" representation with a string value. -type MIMEDataBundle map[string]interface{} - -// NewTextMIMEDataBundle creates a MIMEDataBundle that only contains a text representation described -// the the parameter 'value'. -func NewTextMIMEDataBundle(value string) MIMEDataBundle { - return MIMEDataBundle{ +// newTextMIMEDataBundle creates a bundledMIMEData that only contains a text representation described +// by the value parameter. +func newTextBundledMIMEData(value string) bundledMIMEData { + return bundledMIMEData{ "text/plain": value, } } -type KernelStatus string - -const ( - KernelStarting KernelStatus = "starting" - KernelBusy = "busy" - KernelIdle = "idle" -) - -// PublishKernelStatus publishes a status message notifying front-ends of the state the kernel is in. -func (receipt *msgReceipt) PublishKernelStatus(status KernelStatus) error { +// PublishKernelStatus publishes a status message notifying front-ends of the state the kernel is in. Supports +// states "starting", "busy", and "idle". +func (receipt *msgReceipt) PublishKernelStatus(status string) error { return receipt.Publish("status", struct { - ExecutionState KernelStatus `json:"execution_state"` + ExecutionState string `json:"execution_state"` }{ ExecutionState: status, }, @@ -264,13 +243,13 @@ func (receipt *msgReceipt) PublishExecutionInput(execCount int, code string) err func (receipt *msgReceipt) PublishExecutionResult(execCount int, output string) error { return receipt.Publish("execute_result", struct { - ExecCount int `json:"execution_count"` - Data MIMEDataBundle `json:"data"` - Metadata MIMEDataBundle `json:"metadata"` + ExecCount int `json:"execution_count"` + Data bundledMIMEData `json:"data"` + Metadata bundledMIMEData `json:"metadata"` }{ ExecCount: execCount, - Data: NewTextMIMEDataBundle(output), - Metadata: make(MIMEDataBundle), + Data: newTextBundledMIMEData(output), + Metadata: make(bundledMIMEData), }, ) } @@ -289,24 +268,3 @@ func (receipt *msgReceipt) PublishExecutionError(err string, trace []string) err }, ) } - -type Stream string - -const ( - StreamStdout Stream = "stdout" - StreamStderr = "stderr" -) - -// PublishWriteStream prints the data string to a stream on the front-end. This is -// either `StreamStdout` or `StreamStderr`. -func (receipt *msgReceipt) PublishWriteStream(stream Stream, data string) error { - return receipt.Publish("stream", - struct { - Stream Stream `json:"name"` - Data string `json:"text"` - }{ - Stream: stream, - Data: data, - }, - ) -}