有没有办法将图像直接传送到温和的响应流?

时间:2016-06-14 06:03:29

标签: f# suave

我想在服务器端动态生成图像并将其发送到浏览器。

目前,我使用MemoryStream将其转换为byte array,然后通常使用api api。见下文:

let draw (text:string) =

  let redBr = new SolidBrush(Color.Red)
  let whiteBr = new SolidBrush(Color.White)
  let i = new Bitmap(600,400)
  let g = Graphics.FromImage i
  let f = new Font("Courier", float32 <| 24.0)
  g.FillRectangle(whiteBr, float32 <| 0.0, float32 <| 0.0, float32 <| 600.0, float32 <| 400.0 )
  g.DrawString(text, f, redBr, float32 <| 10.0, float32 <| 40.0)
  g.Flush()
  let ms = new System.IO.MemoryStream()
  i.Save(ms, ImageFormat.Png)
  ms.ToArray()

let app : WebPart =
  Writers.setMimeType "image/png"
  >=> (Successful.ok <| draw "bibi")

我觉得如果suave允许我们直接管道到响应流,可以避免MemoryStream部分。

谢谢!

3 个答案:

答案 0 :(得分:3)

你基本上是这样做的:

open System.IO
open Suave
open Suave.Sockets
open Suave.Sockets.Control

path "/byte-stream" >=> (fun ctx ->

  let write (conn, _) = socket {
    use ms = new MemoryStream()
    ms.Write([| 1uy; 2uy; 3uy |], 0, 3)
    ms.Seek(0L, SeekOrigin.Begin) |> ignore
    // do things here
    let! (_,conn) = asyncWriteLn (sprintf "Content-Length: %d\r\n" ms.Length) conn
    let! conn = flush conn
    do! transferStream conn ms
    return conn
  }

  { ctx with
      response =
        { ctx.response with
            status = HTTP_200.status
            content = SocketTask write } }
  |> succeed
)

答案 1 :(得分:1)

我假设您担心分配不必要的对象。我认为这种担忧值得称赞。

你可能会想到的是一个Bitmap API,其中PNG图像的字节是通过Stream<byte>提供的,然后Bitmap API在套接字需要时生成。

System.Drawing似乎不支持这种行为,可能是WIC(.NET包装器通过优秀的SharpDX库存在)。

然而,这意味着在传输期间保持可能昂贵的对象(位图,画笔等)存活。字节数组可能是存储结果的更有效方式。

为避免不必要地分配对象,另一种方法是缓存它们。这有点问题,因为System.Drawing对象是可变的,并且不能安全地从多个线程使用。但是,您可以使用ThreadLocal为每个线程创建一个缓存。

在下面的示例代码中,大多数对象都按Thread进行缓存。每次调用draw时创建的唯一对象是返回的字节数组,但这可能是PNG数据的有效存储(调用System.Drawing可能会分配对象但我们无法控制它)。由于我没有找到一种方法来监听线程的“死亡”,这意味着当线程不再需要对象时,使用dispose方法手动处理对象非常重要。

希望这很有意思

open System
open System.Drawing
open System.Drawing.Imaging
open System.IO
open System.Threading

module BitmapCreator =
  module internal Details =
    let dispose (d : IDisposable) =
      if d <> null then
        try
          d.Dispose ()
        with
        | e -> () // TODO: log

    // state is ThreadLocal, it means the resources gets initialized once per thread
    let state =
      let initializer () =
        // Allocate all objects needed for work
        let font        = new Font("Courier", 24.0F)
        let red         = new SolidBrush(Color.Red)
        let white       = new SolidBrush(Color.White)
        let bitmap      = new Bitmap(600,400)
        let g           = Graphics.FromImage bitmap
        let ms          = new MemoryStream 1024
        // disposer should be called when Thread is terminating to reclaim
        //  resources as fast as possible
        let disposer () =
          dispose ms
          dispose g
          dispose bitmap
          dispose white
          dispose red
          dispose font
        font, red, white, bitmap, g, ms, disposer

      new ThreadLocal<_>(initializer)

  // Draws text on a bitmap and returns that as a byte array
  let draw text =
    // Grab the state for the current thread
    let font, red, white, bitmap, g, ms, _ = Details.state.Value

    g.FillRectangle(white, 0.0F, 0.0F, 600.0F, 400.0F)
    g.DrawString(text, font, red, 10.0F, 40.0F)
    g.Flush()

    // Resets the memory stream
    //  The capacity is preserved meaning as long as the generated
    //  images is equal or smaller in size no realloc is needed
    ms.Seek (0L, SeekOrigin.Begin) |> ignore
    ms.SetLength 0L

    bitmap.Save(ms, ImageFormat.Png)

    // Here a new array is allocated per call
    //  Depending on how FillRectangle/DrawString works this is hopefully our
    //  only allocation
    ms.ToArray()

  // Disposes all BitmapCreator resources held by the current thread
  let dispose () =
    let _, _, _, _, _, _, disposer = Details.state.Value
    disposer ()

[<EntryPoint>]
let main argv =
  // Saves some bitmaps to file, the name include the thread pid in order
  //  to not overwrite other threads' images
  let save () =
    let texts = [|"Hello"; "There"|]
    let tid   = Thread.CurrentThread.ManagedThreadId
    for text in texts do
      File.WriteAllBytes (sprintf "%s_%d.png" text tid, BitmapCreator.draw text)

  // Runs a in other thread, disposes BitmapCreator resources when done
  let runInOtherThread (a : unit -> unit) =
    let a () =
      try
        a ()
      finally
        BitmapCreator.dispose ()
    let thread = Thread a
    thread.Start ()
    thread.Join ()

  Environment.CurrentDirectory <- AppDomain.CurrentDomain.BaseDirectory

  try
    save () // Here we allocate BitmapCreator resources
    save () // Since the same thread is calling the resources will reused
    runInOtherThread save // New thread, new resources
    runInOtherThread save // New thread, new resources
  finally
    BitmapCreator.dispose ()

  0

答案 2 :(得分:0)

这个问题更普遍地涉及将图像直接保存到.NET中的套接字,而不是特定于F#或Suave。在这个链接上有一些讨论,我认为基本上可以得出结论,你将首先考虑创建一个临时缓冲区,无论是通过MemoryStream还是在图像上调用.ToArray()。 Sending and receiving an image over sockets with C#