Simple-Web-Server icon indicating copy to clipboard operation
Simple-Web-Server copied to clipboard

Does it support upload/download file?

Open pengweichu opened this issue 8 years ago • 15 comments

Does it support upload/download file?

pengweichu avatar Dec 16 '15 03:12 pengweichu

The default resource example shows one way to downloads files. For upload, you'd have to create a post resource that handles this. This library is quite low level in order to keep it simple and make it easy for developers to change it or improve it according to their need.

eidheim avatar Dec 16 '15 11:12 eidheim

Thanks for your reply, but we are not familiar with web server programming, it's possible require you add "upload file" feature then we can pay for it or donate for this project.

We just need the upload file example or a low level handler for it :)

BR,

pengweichu avatar Dec 16 '15 15:12 pengweichu

It is not that hard actually, just read a set number of bytes from the request->content stream and store it in a file using write with the byte buffer received from the request->content stream. In a way, the reverse of the default_resource example. I do not have time to make this example sorry!

eidheim avatar Dec 16 '15 15:12 eidheim

But when I think about it, I would not use this library to upload large files, since the request->content stream would have the entire file stored in memory.

eidheim avatar Dec 16 '15 15:12 eidheim

Thanks!

pengweichu avatar Dec 16 '15 15:12 pengweichu

We should actually add a max Content-Length variable, and not accept larger posts than that variable.

eidheim avatar Dec 16 '15 15:12 eidheim

I added a new file to support upload file, and add a max Content-Length variable: if (content_length > max_content_length) {//not accept larger posts than max_content_length if (timeout_content > 0) timer->cancel(); find_resource(socket, request); return; } But when the content_length > max_content_length, will return a error page: The connection was reset The connection to the server was reset while the page was loading.

The site could be temporarily unavailable or too busy. Try again in a few moments.
If you are unable to load any pages, check your computer's network connection.
If your computer or network is protected by a firewall or proxy, make sure that Firefox is permitted to access the Web.

How to process when content_length > max_content_length>? I has send a Pull requests about upload file.

fastxyf avatar Dec 21 '15 04:12 fastxyf

Does the server support handling chunked transfer?

Lucas-W avatar Aug 22 '16 12:08 Lucas-W

@Lucas-W No chunked transfer would have to be implemented by you on top of Simple-Web-Server. See the default_resource example on how to send a file piece by piece. One could extend this example to include chunked transfer.

eidheim avatar Aug 22 '16 13:08 eidheim

I write the upload method like this: server.resource["^/upload"]["POST"] = [](shared_ptrHttpServer::Response response, shared_ptrHttpServer::Requestrequest) { //auto ifs = make_shared(); //ifs->open("D:/VC\boostServer\boostServer\web\save.jpg", ios::out | ios::binary); auto wfs = make_shared("D:/VC\boostServer\boostServer\web\save.jpg"); stringstream buf; buf << request->content.rdbuf(); // I think this code read the uplaod filestream is not right buf.seekg(0, std::ios::end); int bufLength = buf.tellg(); buf.seekg(0, std::ios::beg); char * pBuff = NULL; pBuff = new char[bufLength]; memset(pBuff, 0, bufLength); buf.read(pBuff, bufLength); //char m_buf[102400]; //buf.read(m_buf, buf.tellp()); wfs->write(pBuff, bufLength);

	*response << "HTTP/1.1 200 OK\r\nAccess-Control-Allow-Origin:*\r\nContent-Type:application/x-jpg" << "\r\n\r\n";
};

but,the save file is not right ,who can solve this problem

xmh0511 avatar Nov 21 '16 14:11 xmh0511

I would just suggest to be carefull when using "Access-Control-Allow-Origin:*". There's no point of using it here, this suggest you've generalised its usage and this sound like a bad idea.

sebt3 avatar Jan 04 '17 15:01 sebt3

I have the some problem too. When I try to post a file and the request->content.rdbuf() is mixed with form content. Like

-----------------------------1780600374882607152068485472 Content-Disposition: form-data; name="upload"; filename="u=3770957164,2342922209&fm=27&gp=0.jpg" Content-Type: image/jpeg

yangruixuan avatar Sep 02 '17 09:09 yangruixuan

@yangruixuan This is due to the form attribute enctype="multipart/form-data" (you can read more about this in https://tools.ietf.org/html/rfc7578). You would have to decode this yourself at the moment. Not sure if we should support this encoding, that is, if it is part of the scope of this project, but I'll give it some more thought.

eidheim avatar Sep 02 '17 12:09 eidheim

Accepting posted file(s) from a web browser was a bit more complex than I initially thought. This is due to the strange multipart/form-data that needs to be supported. Why this standard does not include length of file is strange and adds to the complexity, but anyway, here is an example supporting both text/binary files in a decent way using the latest master (please test before production use):

#include "server_http.hpp"

typedef SimpleWeb::Server<SimpleWeb::HTTP> HttpServer;

int main() {
  HttpServer server;
  server.config.port = 8080;
  server.default_resource["GET"] = [](std::shared_ptr<HttpServer::Response> response, std::shared_ptr<HttpServer::Request> /*request*/) {
    response->write(R"(<html>
<body>
<form action="upload" method="post" enctype="multipart/form-data">
  <input type="file" name="file" multiple>
  <input type="submit">
</form>

</body>
</html>    
)");
  };

  server.resource["/upload"]["POST"] = [](std::shared_ptr<HttpServer::Response> response, std::shared_ptr<HttpServer::Request> request) {
    std::string buffer;
    buffer.resize(131072);

    std::string boundary;
    if(!getline(request->content, boundary)) {
      response->write(SimpleWeb::StatusCode::client_error_bad_request);
      return;
    }

    // go through all content parts
    while(true) {
      std::stringstream file; // std::stringstream is used as example output type
      std::string filename;

      auto header = SimpleWeb::HttpHeader::parse(request->content);
      auto header_it = header.find("Content-Disposition");
      if(header_it != header.end()) {
        auto content_disposition_attributes = SimpleWeb::HttpHeader::FieldValue::SemicolonSeparatedAttributes::parse(header_it->second);
        auto filename_it = content_disposition_attributes.find("filename");
        if(filename_it != content_disposition_attributes.end()) {
          filename = filename_it->second;

          bool add_newline_next = false; // there is an extra newline before content boundary, this avoids adding this extra newline to file
          // store file content in variable file
          while(true) {
            request->content.getline(&buffer[0], static_cast<std::streamsize>(buffer.size()));
            if(request->content.eof()) {
              response->write(SimpleWeb::StatusCode::client_error_bad_request);
              return;
            }
            auto size = request->content.gcount();

            if(size >= 2 && (static_cast<size_t>(size - 1) == boundary.size() || static_cast<size_t>(size - 1) == boundary.size() + 2) && // last boundary ends with: --
               std::strncmp(buffer.c_str(), boundary.c_str(), boundary.size() - 1 /*ignore \r*/) == 0 &&
               buffer[static_cast<size_t>(size) - 2] == '\r') // buffer must also include \r at end
              break;

            if(add_newline_next) {
              file.put('\n');
              add_newline_next = false;
            }

            if(!request->content.fail()) { // got line or section that ended with newline
              file.write(buffer.c_str(), size - 1); // size includes newline character, but buffer does not
              add_newline_next = true;
            }
            else
              file.write(buffer.c_str(), size);

            request->content.clear(); // clear stream state
          }

          std::cout << "filename: " << filename << std::endl
                    << "file content:" << std::endl
                    << file.str() << std::endl;
        }
      }
      else { // no more parts
        response->write(); // Write empty success response
        return;
      }
    }
  };

  server.start();
}

edit: HttpHeader::FieldValue::SemicolonSeparated renamed to HttpHeader::FieldValue::SemicolonSeparatedAttributes, and the returned attribute values from parse are now also percent-decoded

eidheim avatar Sep 04 '17 14:09 eidheim

Great job! Thanks!

yangruixuan avatar Sep 13 '17 11:09 yangruixuan