FsInteractiveService


Running snippets via HTTP API

This page gives a very brief overview of the commands exposed by the FsInteractiveService.exe server. The server exposes endpoints:

  • /output for getting output that was printed by F# interactive
  • /eval for evaluating expressions and interactions
  • /cancel for cancelling running computation
  • /reset for restarting the F# Interactive (this is limited though; see below)
  • endpoints for IntelliSense are discussed in a separate document

Starting the server

For the purpose of the demo, we start the server using Process.Start. The process takes one optional parameter, which is the binding address and port to be used. Either part may also be omitted, in which case the defaults are used (127.0.0.1 and 8707, respectively).

1: 
2: 
3: 
4: 
5: 
6: 
let fsiservice = 
  ProcessStartInfo
    ( FileName = root + "FsInteractiveService.exe", 
      Arguments="18082",
      CreateNoWindow=true,UseShellExecute=false )
  |> Process.Start

Here, we start the process at a port 18082. You could specify address and port by providing an argument in the format 0.0.0.0:80. When the parameter does not contain :, it is treated either as a port or as an address, depending on whether it can be parsed as an integer.

Reading FSI output

F# Interactive can print output at any time (e.g. from a background async workflow or after starting). You can get the output printed since the last time by calling:

1: 
2: 
Http.RequestString
  ("http://localhost:18082/output", httpMethod="POST")
{
  "result": "output",
  "output": "\r\nF# Interactive for F# 4.0 (private)\r\nFreely dist...",
  "details": null
}

In the resulting JSON, the result is set to output and you can find the printed message in output.

Evaluating code

To evaluate code, you can call /eval with a JSON that contains the file name, line of the source code in the source file and the code itself (the file does not have to exist).

1: 
2: 
3: 
Http.RequestString
  ( "http://localhost:18082/eval", httpMethod="POST", 
    body=TextRequest """{ "file":"/a.fsx", "line":10, "code":"1+1"}""")
{
  "result": "success",
  "output": "val it : int = 2\r\n",
  "details": {
    "string": "2",
    "html": null,
    "warnings": []
  }
}

The result is set to success and F# Interactive prints the returned value into output. If the evaluates code is an expression, the string field in details contains the value formatted using ToString. If your code prints something, you will find the printed result in output too:

1: 
2: 
3: 
Http.RequestString
  ( "http://localhost:18082/eval", httpMethod="POST", 
    body=TextRequest """{ "file":"/a.fsx", "line":10, "code":"printfn \"Hi!\""}""")
{
  "result": "success",
  "output": "Hi!\r\nval it : unit = ()\r\n",
  "details": {
    "string": null,
    "html": null,
    "warnings": []
  }
}

Note that this is still an expression (that can be formatted) and so the output includes the it value, but string is null. Finally, the following snippet evaluates code that throws an exception:

1: 
2: 
3: 
4: 
Http.RequestString
  ( "http://localhost:18082/eval", httpMethod="POST", 
    body=TextRequest """{ "file":"/a.fsx", "line":10, 
      "code":"1 + failwith \"Oops!\""}""")
{
  "result": "exception",
  "output": "",
  "details": "System.Exception: Oops!\r\n   at <StartupCode$FSI_00..."
}

In this case, the result is exception and you can find the formatted exception (using ToString) in the details field.

Errors and warnings

The following tries to evaluate 1+1.0, which is a type error:

1: 
2: 
3: 
4: 
Http.RequestString
  ( "http://localhost:18082/eval", httpMethod="POST", 
    body=TextRequest """{ "file":"/a.fsx", "line":10, 
      "code":"1+1.0"}""")
{
  "result": "error",
  "output": "Stopped due to error\n",
  "details": [
    {
      "startLine": 10,
      "endLine": 10,
      "startColumn": 2,
      "endColumn": 5,
      "fileName": "/a.fsx",
      "severity": "error",
      "errorNumber": 1,
      "message": "The type 'float' does not match the type 'int'"
    },
    {
      "startLine": 10,
      "endLine": 10,
      "startColumn": 1,
      "endColumn": 2,
      "fileName": "/a.fsx",
      "severity": "error",
      "errorNumber": 43,
      "message": "The type 'float' does not match the type 'int'"
    }
  ]
}

For errors, the result is errror and details contains an array with detailed information about the individual messages. Note that the line number is calculated relatively to the number you provide in the input. Next, let's look at code that runs, but with warnings:

1: 
2: 
3: 
4: 
Http.RequestString
  ( "http://localhost:18082/eval", httpMethod="POST", 
    body=TextRequest """{ "file":"/a.fsx", "line":10, 
      "code":"1 + 1:>int"}""")
{
  "result": "success",
  "output": "val it : int = 2\r\n",
  "details": {
    "string": "2",
    "html": null,
    "warnings": [
      {
        "startLine": 10,
        "endLine": 10,
        "startColumn": 0,
        "endColumn": 10,
        "fileName": "/a.fsx",
        "severity": "warning",
        "errorNumber": 59,
        "message": "The type 'int' does not have any proper subtypes a..."
      },
      {
        "startLine": 10,
        "endLine": 10,
        "startColumn": 0,
        "endColumn": 10,
        "fileName": "/a.fsx",
        "severity": "warning",
        "errorNumber": 66,
        "message": "This upcast is unnecessary - the types are identic..."
      }
    ]
  }
}

This evaluates fine so result is success, but the details field contains warnings where you can find an array of warnings (in the same format as errors above).

Cancelling computation

If you run an infinite loop in the F# interactive process, you can cancel it by calling /cancel. The following starts a request (in background) with an infinite loop and then cancels it later:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
async {
  Http.RequestString
    ( "http://localhost:18082/eval", httpMethod="POST", 
      body=TextRequest """{ "file":"/a.fsx", "line":10, 
        "code":"while true do ()"}""") } |> Async.Start

System.Threading.Thread.Sleep(1000)
Http.RequestString
  ("http://localhost:18082/cancel", httpMethod="POST")
{
  "result": "canceled",
  "output": "",
  "details": null
}

You can also reset the F# Interactive service by calling /reset, but that has limitations. It resets whatever F# Interactive is doing, but it does not kill all background processes that it might have started (like async workflow started using Async.Start), so it is probably better to just kill the process and restart it completely.

Adding HTML printers

As discussed in HTML printer docs, you can use AddHtmlPrinter to register printer for formatting values as HTML. The FsInteractiveService defines a special symbol HAS_FSI_ADDHTMLPRINTER that can be used to call the method only when running in the FsInteractiveService context.

The method registers printers based on types. For example, the following silly example defines a printer that makes integers bold:

1: 
2: 
3: 
4: 
5: 
Http.RequestString
  ( "http://localhost:18082/eval", httpMethod="POST", 
    body=TextRequest """{ "file":"/a.fsx", "line":10,   
      "code":"fsi.AddHtmlPrinter(fun (n:int) -> 
        Seq.empty, sprintf \"<b>%d</b>\" n)"}""")

This evaluates without returning anything interesting. Now, we can evaluate an expression that returns an int:

1: 
2: 
3: 
4: 
Http.RequestString
  ( "http://localhost:18082/eval", httpMethod="POST", 
    body=TextRequest """{ "file":"/a.fsx", "line":10, 
      "code":"42"}""")
{
  "result": "success",
  "output": "val it : int = 42\r\n",
  "details": {
    "string": "42",
    "html": {
      "body": "<b>42</b>",
      "parameters": []
    },
    "warnings": []
  }
}

When HTML printer is registered for a type, the details field will incldue the result of calling the HTML printer in the html field. You can see that we got "<b>42</b>" here! The Seq.empty parameter here specifies that no additional styles or scripts are required - for more information, see the AddHtmlPrinter documentation page.

Wrapping up

Do not forget to kill the FsInteractiveService.exe process at the end...

1: 
fsiservice.Kill()
Multiple items
namespace FSharp

--------------------
namespace Microsoft.FSharp
Multiple items
namespace FSharp.Data

--------------------
namespace Microsoft.FSharp.Data
namespace System
namespace System.Diagnostics
val root : string

Full name: Http.root
val fsiservice : Process

Full name: Http.fsiservice
Multiple items
type ProcessStartInfo =
  new : unit -> ProcessStartInfo + 2 overloads
  member Arguments : string with get, set
  member CreateNoWindow : bool with get, set
  member Domain : string with get, set
  member EnvironmentVariables : StringDictionary
  member ErrorDialog : bool with get, set
  member ErrorDialogParentHandle : nativeint with get, set
  member FileName : string with get, set
  member LoadUserProfile : bool with get, set
  member Password : SecureString with get, set
  ...

Full name: System.Diagnostics.ProcessStartInfo

--------------------
ProcessStartInfo() : unit
ProcessStartInfo(fileName: string) : unit
ProcessStartInfo(fileName: string, arguments: string) : unit
Multiple items
type Process =
  inherit Component
  new : unit -> Process
  member BasePriority : int
  member BeginErrorReadLine : unit -> unit
  member BeginOutputReadLine : unit -> unit
  member CancelErrorRead : unit -> unit
  member CancelOutputRead : unit -> unit
  member Close : unit -> unit
  member CloseMainWindow : unit -> bool
  member EnableRaisingEvents : bool with get, set
  member ExitCode : int
  ...

Full name: System.Diagnostics.Process

--------------------
Process() : unit
Process.Start(startInfo: ProcessStartInfo) : Process
Process.Start(fileName: string) : Process
Process.Start(fileName: string, arguments: string) : Process
Process.Start(fileName: string, userName: string, password: System.Security.SecureString, domain: string) : Process
Process.Start(fileName: string, arguments: string, userName: string, password: System.Security.SecureString, domain: string) : Process
type Http =
  private new : unit -> Http
  static member private AppendQueryToUrl : url:string * query:(string * string) list -> string
  static member AsyncRequest : url:string * ?query:(string * string) list * ?headers:seq<string * string> * ?httpMethod:string * ?body:HttpRequestBody * ?cookies:seq<string * string> * ?cookieContainer:CookieContainer * ?silentHttpErrors:bool * ?responseEncodingOverride:string * ?customizeHttpRequest:(HttpWebRequest -> HttpWebRequest) -> Async<HttpResponse>
  static member AsyncRequestStream : url:string * ?query:(string * string) list * ?headers:seq<string * string> * ?httpMethod:string * ?body:HttpRequestBody * ?cookies:seq<string * string> * ?cookieContainer:CookieContainer * ?silentHttpErrors:bool * ?customizeHttpRequest:(HttpWebRequest -> HttpWebRequest) -> Async<HttpResponseWithStream>
  static member AsyncRequestString : url:string * ?query:(string * string) list * ?headers:seq<string * string> * ?httpMethod:string * ?body:HttpRequestBody * ?cookies:seq<string * string> * ?cookieContainer:CookieContainer * ?silentHttpErrors:bool * ?responseEncodingOverride:string * ?customizeHttpRequest:(HttpWebRequest -> HttpWebRequest) -> Async<string>
  static member private InnerRequest : url:string * toHttpResponse:(string -> int -> string -> string -> string -> 'a0 option -> Map<string,string> -> Map<string,string> -> Stream -> Async<'a1>) * ?query:(string * string) list * ?headers:seq<string * string> * ?httpMethod:string * ?body:HttpRequestBody * ?cookies:seq<string * string> * ?cookieContainer:CookieContainer * ?silentHttpErrors:bool * ?responseEncodingOverride:'a0 * ?customizeHttpRequest:(HttpWebRequest -> HttpWebRequest) -> Async<'a1>
  static member Request : url:string * ?query:(string * string) list * ?headers:seq<string * string> * ?httpMethod:string * ?body:HttpRequestBody * ?cookies:seq<string * string> * ?cookieContainer:CookieContainer * ?silentHttpErrors:bool * ?responseEncodingOverride:string * ?customizeHttpRequest:(HttpWebRequest -> HttpWebRequest) -> HttpResponse
  static member RequestStream : url:string * ?query:(string * string) list * ?headers:seq<string * string> * ?httpMethod:string * ?body:HttpRequestBody * ?cookies:seq<string * string> * ?cookieContainer:CookieContainer * ?silentHttpErrors:bool * ?customizeHttpRequest:(HttpWebRequest -> HttpWebRequest) -> HttpResponseWithStream
  static member RequestString : url:string * ?query:(string * string) list * ?headers:seq<string * string> * ?httpMethod:string * ?body:HttpRequestBody * ?cookies:seq<string * string> * ?cookieContainer:CookieContainer * ?silentHttpErrors:bool * ?responseEncodingOverride:string * ?customizeHttpRequest:(HttpWebRequest -> HttpWebRequest) -> string

Full name: FSharp.Data.Http
static member Http.RequestString : url:string * ?query:(string * string) list * ?headers:seq<string * string> * ?httpMethod:string * ?body:HttpRequestBody * ?cookies:seq<string * string> * ?cookieContainer:System.Net.CookieContainer * ?silentHttpErrors:bool * ?responseEncodingOverride:string * ?customizeHttpRequest:(System.Net.HttpWebRequest -> System.Net.HttpWebRequest) -> string
union case HttpRequestBody.TextRequest: string -> HttpRequestBody
val async : AsyncBuilder

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
Multiple items
type Async
static member AsBeginEnd : computation:('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)
static member AwaitEvent : event:IEvent<'Del,'T> * ?cancelAction:(unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)
static member AwaitIAsyncResult : iar:IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
static member AwaitTask : task:Task -> Async<unit>
static member AwaitTask : task:Task<'T> -> Async<'T>
static member AwaitWaitHandle : waitHandle:WaitHandle * ?millisecondsTimeout:int -> Async<bool>
static member CancelDefaultToken : unit -> unit
static member Catch : computation:Async<'T> -> Async<Choice<'T,exn>>
static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromContinuations : callback:(('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T>
static member Ignore : computation:Async<'T> -> Async<unit>
static member OnCancel : interruption:(unit -> unit) -> Async<IDisposable>
static member Parallel : computations:seq<Async<'T>> -> Async<'T []>
static member RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:CancellationToken -> 'T
static member Sleep : millisecondsDueTime:int -> Async<unit>
static member Start : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions * ?cancellationToken:CancellationToken -> Task<'T>
static member StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
static member StartChildAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions -> Async<Task<'T>>
static member StartImmediate : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartWithContinuations : computation:Async<'T> * continuation:('T -> unit) * exceptionContinuation:(exn -> unit) * cancellationContinuation:(OperationCanceledException -> unit) * ?cancellationToken:CancellationToken -> unit
static member SwitchToContext : syncContext:SynchronizationContext -> Async<unit>
static member SwitchToNewThread : unit -> Async<unit>
static member SwitchToThreadPool : unit -> Async<unit>
static member TryCancelled : computation:Async<'T> * compensation:(OperationCanceledException -> unit) -> Async<'T>
static member CancellationToken : Async<CancellationToken>
static member DefaultCancellationToken : CancellationToken

Full name: Microsoft.FSharp.Control.Async

--------------------
type Async<'T>

Full name: Microsoft.FSharp.Control.Async<_>
static member Async.Start : computation:Async<unit> * ?cancellationToken:System.Threading.CancellationToken -> unit
namespace System.Threading
Multiple items
type Thread =
  inherit CriticalFinalizerObject
  new : start:ThreadStart -> Thread + 3 overloads
  member Abort : unit -> unit + 1 overload
  member ApartmentState : ApartmentState with get, set
  member CurrentCulture : CultureInfo with get, set
  member CurrentUICulture : CultureInfo with get, set
  member DisableComObjectEagerCleanup : unit -> unit
  member ExecutionContext : ExecutionContext
  member GetApartmentState : unit -> ApartmentState
  member GetCompressedStack : unit -> CompressedStack
  member GetHashCode : unit -> int
  ...

Full name: System.Threading.Thread

--------------------
System.Threading.Thread(start: System.Threading.ThreadStart) : unit
System.Threading.Thread(start: System.Threading.ParameterizedThreadStart) : unit
System.Threading.Thread(start: System.Threading.ThreadStart, maxStackSize: int) : unit
System.Threading.Thread(start: System.Threading.ParameterizedThreadStart, maxStackSize: int) : unit
System.Threading.Thread.Sleep(timeout: System.TimeSpan) : unit
System.Threading.Thread.Sleep(millisecondsTimeout: int) : unit
Process.Kill() : unit
F# Project
Fork me on GitHub