Webcc: Lightweight C++ HTTP Library

Keywords: C++ Session JSON github REST

Webcc is based on Boost Asio The lightweight C++ HTTP library is developed, which supports both client and server.

A few years ago, it only supported simple SOAP calls (cSoap), which gradually evolved into a pure HTTP library. At present, it has more than 250 code submissions and is still improving. It has been applied in our company's products, defeating Qt's QtHttpServer. Almost all C++ has the best HTTP libraries.

Compilation Guide At present, there is only an English version.

Code Warehouse: https://github.com/sprinfall/webcc . Please confirm the link, others fork's warehouse is not the latest.

Functional overview

  • Cross-platform: Windows, Linux and MacOS
  • Simple and easy-to-use client API, drawing on Python's requests Program Library
  • Supports SSL/HTTPS and relies on OpenSSL (optional)
  • Support GZip compression, depending on Zlib (optional)
  • Keep-Alive
  • Streaming

    • Client: Large files can be uploaded and downloaded
    • Server: Servo and receive large files
  • Support Basic & Token Authentication/Authorization
  • Timeout control (currently client only)
  • Code compliance Google C++ Style
  • Quality assurance through automated testing and unit testing
  • No memory leak( VLD Detection)

Client API

Let's start with a complete example:

#include <iostream>

#include "webcc/client_session.h"
#include "webcc/logger.h"

int main() {
  // First configure log output (to console/command line)
  WEBCC_LOG_INIT("", webcc::LOG_CONSOLE);
  
  // Create a session
  webcc::ClientSession session;

  try {
    // Initiate an HTTP GET request
    auto r = session.Get("http://httpbin.org/get");

    // Output response data
    std::cout << r->data() << std::endl;

  } catch (const webcc::Error& error) {
    // exception handling
    std::cerr << error << std::endl;
  }

  return 0;
}

Because Get() is just a shortcut to Request(), calling Request() directly is more complicated:

auto r = session.Request(webcc::RequestBuilder{}.
                         Get("http://httpbin.org/get")
                         ());

Here is an additional auxiliary class called RequestBuilder, which is used to concatenate various parameters and finally regenerate a request object. Be careful not to omit the () operator.

Both Get and Request() accept URL query parameters:

// Query parameters are specified by std::vector together and key values appear in pairs
session.Get("http://httpbin.org/get", { "key1", "value1", "key2", "value2" });

// Query parameters are specified one by one by Query().
session.Request(webcc::RequestBuilder{}.
                Get("http://httpbin.org/get").
                Query("key1", "value1").
                Query("key2", "value2")
                ());

Adding extra headers is also simple:

session.Get("http://httpbin.org/get",
            {"key1", "value1", "key2", "value2"},
            {"Accept", "application/json"});  // Also std::vector
                
session.Request(webcc::RequestBuilder{}.
                Get("http://httpbin.org/get").
                Query("key1", "value1").
                Query("key2", "value2").
                Header("Accept", "application/json")
                ());

There is no difference between accessing HTTPS and HTTP, which is transparent to users:

session.Get("https://httpbin.org/get");

Note: For HTTPS/SSL support, the compilation option WEBCC_ENABLE_SSL needs to be enabled, and OpenSSL will also be relied on.

Listing GitHub public events is not a problem either:

auto r = session.Get("https://api.github.com/events");

Then, you can parse R - > data () into JSON objects and use any JSON library.

What I use in the sample program is jsoncpp But Webcc itself does not understand JSON. What JSON library to use is entirely your own choice.

Fast functions (Get(), Post(), etc.) are convenient to use, but the parameters are limited and there are many restrictions. RequestBuilder is more flexible and powerful, it provides many functions for you to customize the request.

To list "followers" of an authorized GitHub user, either use Basic authentication:

session.Request(webcc::RequestBuilder{}.
                Get("https://api.github.com/user/followers").
                AuthBasic(login, password)  // Should be replaced with a specific account number, password
                ());

Or use Token authentication:

session.Request(webcc::RequestBuilder{}.
                Get("https://api.github.com/user/followers").
                AuthToken(token)  // It should be replaced by a specific legal token
                ());

Although Keep-Alive is a good feature, you can turn it off manually:

auto r = session.Request(webcc::RequestBuilder{}.
                         Get("http://httpbin.org/get").
                         KeepAlive(false)  // Don't Keep-Alive
                         ());

The API s of other HTTP requests are not much different from GET s.

POST requests require a body, usually a JSON string for the REST API. Let's POST a UTF-8 encoded JSON string:

session.Request(webcc::RequestBuilder{}.
                Post("http://httpbin.org/post").
                Body("{'name'='Adam', 'age'=20}").
                Json().Utf8()
                ());

Webcc can stream large response data to temporary files, which is especially useful when downloading files.

auto r = session.Request(webcc::RequestBuilder{}.
                         Get("http://httpbin.org/image/jpeg")(),
                         true);  // stream = true

// Move streaming files to the target location
r->file_body()->Move("./wolf.jpeg");

Not only download, upload can also stream:

auto r = session.Request(webcc::RequestBuilder{}.
                         Post("http://httpbin.org/post").
                         File(path)  // It should be replaced with a specific file path
                         ());

When POST is used, this file will not be loaded into memory at one time, read a piece of data and send a piece of data until it is sent out.

Note that the Content-Length header is still set to the true size of the file, which is different from the block data form of Transfer-Encoding: chunked.

For more examples and usage, please refer to examples Catalog.

Server API

Hello, World!

Here is a Hello, World! Level service program.
After the program runs, open the browser, enter http://localhost:8080, the page shows Hello, World!.

class HelloView : public webcc::View {
public:
  webcc::ResponsePtr Handle(webcc::RequestPtr request) override {
    if (request->method() == "GET") {
      return webcc::ResponseBuilder{}.OK().Body("Hello, World!")();
    }

    return {};
  }
};

int main() {
  try {
    webcc::Server server(8080);

    server.Route("/", std::make_shared<HelloView>());

    server.Run();

  } catch (const std::exception&) {
    return 1;
  }

  return 0;
}

Explain briefly. A server corresponds to multiple views, and different views correspond to different resources. Views are routed through URLs, and URLs can be regular expressions.

See the complete code examples/hello_world_server.

Let's look at a more complex example.

online bookstore

Assuming you want to create a service about books, provide the following REST API s:

  • Inquiry
  • Add a new book
  • Get details of a Book
  • Update the information of a Book
  • Delete a Book

This is a typical set of CRUD operations.

The first two operations are implemented through BookListView:

ListView, the way DetailView is named, refers to the Django REST Framework. ListView is for a list of resources and DetailView is for a single resource.

class BookListView : public webcc::View {
public:
  webcc::ResponsePtr Handle(webcc::RequestPtr request) override {
    if (request->method() == "GET") {
      return Get(request);
    }

    if (request->method() == "POST") {
      return Post(request);
    }

    return {};
  }
  
private:
  // Inquiry
  webcc::ResponsePtr Get(webcc::RequestPtr request);

  // Add a new book
  webcc::ResponsePtr Post(webcc::RequestPtr request);
};

Other operations are implemented through BookDetailView:

class BookDetailView : public webcc::View {
public:
  webcc::ResponsePtr Handle(webcc::RequestPtr request) override {
    if (request->method() == "GET") {
      return Get(request);
    }

    if (request->method() == "PUT") {
      return Put(request);
    }

    if (request->method() == "DELETE") {
      return Delete(request);
    }

    return {};
  }
  
protected:
  // Get details of a Book
  webcc::ResponsePtr Get(webcc::RequestPtr request);

  // Update the information of a Book
  webcc::ResponsePtr Put(webcc::RequestPtr request);

  // Delete a Book
  webcc::ResponsePtr Delete(webcc::RequestPtr request);
};

Let's pick a function and see:

webcc::ResponsePtr BookDetailView::Get(webcc::RequestPtr request) {
  if (request->args().size() != 1) {
    // NotFound (404) means that the resource specified by the URL is not found.
    // It should also be reasonable to use BadRequest (400) here.
    // However, as you can see later, this view matches the URL of "/ books/(\\ d+)", and the parameters will certainly be fine.
    // So the error handling here is just for prevention and rigorous programming.
    // Webcc does not deal with strongly typed URL parameters, so the code is too complex to write.
    return webcc::ResponseBuilder{}.NotFound()();
  }

  const std::string& book_id = request->args()[0];

  // Find the book by ID, for example, from the database.
  // ...

  if (<Can't find>) {
    return webcc::ResponseBuilder{}.NotFound()();
  }

  // Convert this book into a JSON string and set it as response data.
  return webcc::ResponseBuilder{}.OK().Data(<JsonStringOfTheBook>).
      Json().Utf8()();
}

The last step is to route the URLs to a specific view, and then start running:

int main(int argc, char* argv[]) {
  // ...

  try {
    webcc::Server server(8080);

    server.Route("/books",
                 std::make_shared<BookListView>(),
                 { "GET", "POST" });

    // ID is matched by regular expressions
    server.Route(webcc::R("/books/(\\d+)"),
                 std::make_shared<BookDetailView>(),
                 { "GET", "PUT", "DELETE" });

    // Start running (note: blocking calls)
    server.Run();

  } catch (const std::exception& e) {
    std::cerr << e.what() << std::endl;
    return 1;
  }

  return 0;
}

See Complete Implementation examples/rest_book_server.cc.

Posted by Dolemite50 on Tue, 13 Aug 2019 23:06:48 -0700