On Tue, 2013-09-24 at 14:24 +0200, andrea zambon wrote:
> I am developing a service http with libsoup, but I have a memory problem.
> When you make a request, the service memory increases during transmission
> and decreases only after the closure of the service. Another call and will
> increase again.

See the API docs for Soup.MessageBody.set_accumulate:
http://valadoc.org/libsoup-2.4/Soup.MessageBody.set_accumulate.html

I have no idea if it will work for encodings other than chunked, but I
don't see a reason why you would want to close the connection after
every request, so why not just use chunked?

> The client is in another machine and the data may be very long (2 GB).
> I try to user Soup.MemoryUse.TAKE but I get garbage data.
> The Soup.Buffer is not free when sending a new buffer?

Using the default Soup.Buffer constructor with Soup.MemoryUse.TAKE is
very difficult to get right.  That flag is telling libsoup:

        "The caller has allocated the memory for the SoupBuffer's use;
        libsoup will assume ownership of it and free it (with g_free())
        when it is done with it."

However, Vala isn't actually aware of the interaction between this
parameter and libsoup's memory management.  According to the bindings,
the argument does not transfer ownership, which means Vala will free the
buffer for you, then it is likely to get reallocated somewhere else and
have its contents changed.  Furthermore, when libsoup tries to free it
your program will likely crash.

If you want to transfer ownership, you should use the Soup.Buffer.take
constructor.  By default valac will actually make a copy of the buffer
for you, so if you want to pass along your reference (which will
invalidate your local copy), use the (owned) keyword:

        var buffer = new Soup.Buffer.take ((owned) data);

I'm attaching a version of a server I wrote a while back which you may
want to take a look at.  It uses chunked encoding for larger files,
while smaller files are mmaped and use content length encoding.  It's
also pretty good about doing things asynchronously when possible.


-Evan
public class Server : Soup.Server {
  public string directory { get; construct; }

  private const int BUFFER_SIZE = 1024 * 1024;

  private void write_chunk (Soup.Message msg) {
    unowned GLib.InputStream stream = msg.get_data<GLib.InputStream> ("input-stream");
    uint8[] buffer = new uint8[BUFFER_SIZE];

    stream.read_async.begin (buffer, GLib.Priority.DEFAULT, null, (obj, res) => {
        try {
          buffer.length = (int) stream.read_async.end (res);
          if (buffer.length > 0) {
            msg.response_body.append_take ((owned) buffer);
          } else {
            msg.response_body.complete ();
          }
        } catch (GLib.Error e) {
          GLib.warning (e.message);
          msg.response_body.complete ();
        }

        this.unpause_message (msg);
      });
  }

  private async void handle_async (Soup.Server server, Soup.Message msg, string path, GLib.HashTable<string,string>? query, Soup.ClientContext client) {
    string filename = GLib.Path.build_filename (directory, path);
    GLib.File file = GLib.File.new_for_path (filename);
    GLib.FileInputStream stream;
    uint64 content_length = 0;

    this.pause_message (msg);

    try {
      GLib.FileInfo info = file.query_info (
        string.joinv (",", { GLib.FileAttribute.STANDARD_SIZE,
                             GLib.FileAttribute.STANDARD_CONTENT_TYPE }), 0);

      content_length = info.get_attribute_uint64 (GLib.FileAttribute.STANDARD_SIZE);
      msg.response_headers.append ("Content-type", info.get_attribute_as_string (GLib.FileAttribute.STANDARD_CONTENT_TYPE));

      stream = yield file.read_async ();
    } catch (GLib.Error e) {
      bool e_handled = false;

      if (e is GLib.IOError) {
        if (e is GLib.IOError.NOT_FOUND) {
          msg.set_status (Soup.Status.NOT_FOUND);
          e_handled = true;
        }
      }

      if (!e_handled) {
        msg.set_status (Soup.Status.INTERNAL_SERVER_ERROR);
      }

      msg.response_headers.append ("Content-type", "text/html");
      string reason = GLib.Markup.escape_text (msg.reason_phrase);
      msg.response_body.append (Soup.MemoryUse.COPY, "<html><head><title>%s</title></head><body><h1>%s</h1></body></html>\n".printf (reason, reason).data);
      this.unpause_message (msg);

      return;
    }

    msg.set_status (Soup.Status.OK);

    if (content_length > 0 && content_length < BUFFER_SIZE) {
      try {
        GLib.MappedFile mapped = new GLib.MappedFile (filename, false);
        unowned uint8[] data = (uint8[]) mapped.get_contents ();
        data.length = (int) mapped.get_length ();

        msg.response_headers.set_encoding (Soup.Encoding.CONTENT_LENGTH);
        msg.response_body.append (Soup.MemoryUse.TEMPORARY, data);
        /* This will keep the mapped file alive until the message is
         * destroyed, which allows us to use Soup.MemoryUse.TEMPORARY
         * above and avoid a copy. */
        msg.set_data<GLib.MappedFile> ("memory-mapped-file", (owned) mapped);

        this.unpause_message (msg);

        return;
      } catch (GLib.Error e) { }
    }

    msg.response_headers.set_encoding (Soup.Encoding.CHUNKED);
    msg.response_body.set_accumulate (false);
    msg.set_data<GLib.InputStream> ("input-stream", stream);

    msg.wrote_chunk.connect(write_chunk);
    write_chunk (msg);
  }

  private void handler (Soup.Server server, Soup.Message msg, string path, GLib.HashTable<string,string>? query, Soup.ClientContext client) {
    this.handle_async.begin (server, msg, path, query, client);
  }

  construct {
    this.add_handler ("/", handler);
  }

  public Server (string directory, uint port = 8080) {
    GLib.Object (directory: directory, port: 8080);
  }
}

static int main (string[] args) {
  if (args.length < 2 || args.length > 3) {
    stderr.printf ("USAGE: %s dir [port]\nport defaults to 8080\n", args[0]);
    return -1;
  }

  Server server = new Server (args[1], (args.length >= 3) ? int.parse (args[2]) : 8080);
  server.run ();
  return 0;
}
_______________________________________________
vala-list mailing list
vala-list@gnome.org
https://mail.gnome.org/mailman/listinfo/vala-list

Reply via email to