【问题标题】:Is there a way to pipe an image directly to a suave response stream?有没有办法将图像直接传送到温和的响应流?
【发布时间】:2016-06-19 02:23:04
【问题描述】:

我想在服务器端动态生成一张图片并发送给浏览器。

目前我正在使用MemoryStream 将其转换为byte array,然后使用通常的 suave 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 部分可以避免。

谢谢!

【问题讨论】:

    标签: f# suave


    【解决方案1】:

    你基本上是这样做的:

    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
    )
    

    【讨论】:

    • 嗨@henrik,我想避免MemoryStream。基本上我的方法是有效的。所做的是在MemoryStream 上生成图像Save 之后,然后将其转换为数组,然后作为响应发送。我的感觉是,它可以以某种方式直接作为响应发送。
    • 您可以像我在上面展示的那样手动发送流,或者您可以使用transferStream 的兄弟函数自己将字节写入套接字。这取决于您对 Save 的实施。
    • 这不是我对Save 的实现,而是System.Drawing.Image 类方法。 .net api。我不知道如何访问它。
    【解决方案2】:

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

    您可能追求的是一个位图 API,其中 PNG 图像的字节通过Stream&lt;byte&gt; 提供,然后位图 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
    

    【讨论】:

      【解决方案3】:

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

      【讨论】:

      • 大家好,为了更清楚,在我最初的问题中困扰我的是需要使用额外的对象来访问位图底层数据。因此,虽然图像已经在内存中,但我需要创建一个转换对象,MemoryStream 只是为了将数据复制为数组,然后将其交给 Suave 以将其传递给浏览器。我希望找到与 nodejs 管道类似的东西:nodejs.org/api/stream.html#stream_event_pipe
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-17
      • 2021-05-21
      • 1970-01-01
      • 1970-01-01
      • 2020-01-18
      • 1970-01-01
      相关资源
      最近更新 更多