5 The tutorial assumes that you have installed Wangle and its dependencies. The tutorial will demonstrate how to build an echo server - the "hello world" of distributed systems.
9 Wangle is a client/server application framework to build asynchronous, event-driven modern C++ services. The fundamental abstraction of Wangle is the Pipeline. Once you have fully understood this abstraction, you will be able to write all sorts of sophisticated modern C++ services. Another important abstraction is Service, which is an advanced version of a pipeline but it’s out of scope for this post.
11 ## What is a Pipeline?
13 The pipeline is the most important and powerful abstraction of Wangle. It offers immense flexibility to customize how requests and responses are handled by your service.
15 A pipeline is a chain of request/response handlers that handle upstream (handling request) and downstream (handling response). Once you chain handlers together, it provides an agile way to convert a raw data stream into the desired message type (class) and the inverse -- desired message type to raw data stream.
17 A handler should do one and only one function - just like the UNIX philisophy. If you have a handler that is doing more than one function than you should split it into individual handlers. This is really important for maintainability and flexibility as its common to change your protocol for one reason or the other.
19 All shared state within handlers are not thread-safe. Only use shared state that is guarded by a mutex, atomic lock, etc. If you want to use a thread-safe container then it is recommended to use Folly's lock-free data structures, which can be easily imported because they are a dependency of Wangle and are blazing fast.
23 Now onto writing our first service with Wangle: the Echo Server.
25 Here's the main piece of code in our echo server; it receives a string, prints it to stdout and sends it back downstream in the pipeline. It's really important to add the line delimiter because our pipeline will use a line decoder.
28 // the main logic of our echo server; receives a string and writes it straight
30 class EchoHandler : public HandlerAdapter<std::string> {
32 virtual void read(Context* ctx, std::string msg) override {
33 std::cout << "handling " << msg << std::endl;
34 write(ctx, msg + "\r\n");
39 This needs to be the final handler in the pipeline. Now the definition of the pipeline is needed to handle the requests and responses.
42 // where we define the chain of handlers for each messeage received
43 class EchoPipelineFactory : public PipelineFactory<EchoPipeline> {
45 EchoPipeline::Ptr newPipeline(std::shared_ptr<AsyncTransportWrapper> sock) {
46 auto pipeline = EchoPipeline::create();
47 pipeline->addBack(AsyncSocketHandler(sock));
48 pipeline->addBack(LineBasedFrameDecoder(8192));
49 pipeline->addBack(StringCodec());
50 pipeline->addBack(EchoHandler());
57 It is **very** important to be strict in the order of insertion as they are ordered by insertion. The pipeline has 4 handlers:
59 - **AsyncSocketHandler**
60 - Upstream: Reads a raw data stream from the socket and converts it into a zero-copy byte buffer.
61 - Downstream: Writes the contents of a zero-copy byte buffer to the underlying socket.
62 - **LineBasedFrameDecoder**
63 - Upstream: receives a zero-copy byte buffer and splits on line-endings
64 - Downstream: just passes the byte buffer to AsyncSocketHandler
66 - Upstream: receives a byte buffer and decodes it into a std::string and pass up to the EchoHandler.
67 - Downstream: receives a std::string and encodes it into a byte buffer and pass down to the LineBasedFrameDecoder.
69 - Upstream: receives a std::string and writes it to the pipeline — which will send the message downstream.
70 - Downstream: receives a std::string and forwards it to StringCodec.
72 Now that all needs to be done is plug the pipeline factory into a ServerBootstrap and that’s pretty much it. Bind a port and wait for it to stop.
75 #include <gflags/gflags.h>
77 #include <wangle/bootstrap/ServerBootstrap.h>
78 #include <wangle/channel/AsyncSocketHandler.h>
79 #include <wangle/codec/LineBasedFrameDecoder.h>
80 #include <wangle/codec/StringCodec.h>
82 using namespace folly;
83 using namespace wangle;
85 DEFINE_int32(port, 8080, "echo server port");
87 typedef Pipeline<IOBufQueue&, std::string> EchoPipeline;
89 // the main logic of our echo server; receives a string and writes it straight
91 class EchoHandler : public HandlerAdapter<std::string> {
93 virtual void read(Context* ctx, std::string msg) override {
94 std::cout << "handling " << msg << std::endl;
95 write(ctx, msg + "\r\n");
99 // where we define the chain of handlers for each message received
100 class EchoPipelineFactory : public PipelineFactory<EchoPipeline> {
102 EchoPipeline::Ptr newPipeline(std::shared_ptr<AsyncTransportWrapper> sock) {
103 auto pipeline = EchoPipeline::create();
104 pipeline->addBack(AsyncSocketHandler(sock));
105 pipeline->addBack(LineBasedFrameDecoder(8192));
106 pipeline->addBack(StringCodec());
107 pipeline->addBack(EchoHandler());
108 pipeline->finalize();
113 int main(int argc, char** argv) {
114 gflags::ParseCommandLineFlags(&argc, &argv, true);
116 ServerBootstrap<EchoPipeline> server;
117 server.childPipeline(std::make_shared<EchoPipelineFactory>());
118 server.bind(FLAGS_port);
119 server.waitForStop();
125 We've written an asynchronous C++ server in under 48 LOC.
129 The code for the echo client is very similar to the Echo Server. Here is the main echo handler.
132 // the handler for receiving messages back from the server
133 class EchoHandler : public HandlerAdapter<std::string> {
135 virtual void read(Context* ctx, std::string msg) override {
136 std::cout << "received back: " << msg;
138 virtual void readException(Context* ctx, exception_wrapper e) override {
139 std::cout << exceptionStr(e) << std::endl;
142 virtual void readEOF(Context* ctx) override {
143 std::cout << "EOF received :(" << std::endl;
149 Notice that we override other methods — readException and readEOF. There are few other methods that can be overriden. If you need to handle a particular event, just override the corresponding virtual method.
151 Now onto the client’s pipeline factory. It is identical the server’s pipeline factory apart from _EventBaseHandler_ — which handles writing data from an event loop thread.
154 // chains the handlers together to define the response pipeline
155 class EchoPipelineFactory : public PipelineFactory<EchoPipeline> {
157 EchoPipeline::Ptr newPipeline(std::shared_ptr<AsyncTransportWrapper> sock) {
158 auto pipeline = EchoPipeline::create();
159 pipeline->addBack(AsyncSocketHandler(sock));
161 EventBaseHandler()); // ensure we can write from any thread
162 pipeline->addBack(LineBasedFrameDecoder(8192, false));
163 pipeline->addBack(StringCodec());
164 pipeline->addBack(EchoHandler());
165 pipeline->finalize();
171 What does it looks like when it is all put together for the client?
174 #include <gflags/gflags.h>
177 #include <wangle/bootstrap/ClientBootstrap.h>
178 #include <wangle/channel/AsyncSocketHandler.h>
179 #include <wangle/channel/EventBaseHandler.h>
180 #include <wangle/codec/LineBasedFrameDecoder.h>
181 #include <wangle/codec/StringCodec.h>
183 using namespace folly;
184 using namespace wangle;
186 DEFINE_int32(port, 8080, "echo server port");
187 DEFINE_string(host, "::1", "echo server address");
189 typedef Pipeline<folly::IOBufQueue&, std::string> EchoPipeline;
191 // the handler for receiving messages back from the server
192 class EchoHandler : public HandlerAdapter<std::string> {
194 virtual void read(Context* ctx, std::string msg) override {
195 std::cout << "received back: " << msg;
197 virtual void readException(Context* ctx, exception_wrapper e) override {
198 std::cout << exceptionStr(e) << std::endl;
201 virtual void readEOF(Context* ctx) override {
202 std::cout << "EOF received :(" << std::endl;
207 // chains the handlers together to define the response pipeline
208 class EchoPipelineFactory : public PipelineFactory<EchoPipeline> {
210 EchoPipeline::Ptr newPipeline(std::shared_ptr<AsyncTransportWrapper> sock) {
211 auto pipeline = EchoPipeline::create();
212 pipeline->addBack(AsyncSocketHandler(sock));
214 EventBaseHandler()); // ensure we can write from any thread
215 pipeline->addBack(LineBasedFrameDecoder(8192, false));
216 pipeline->addBack(StringCodec());
217 pipeline->addBack(EchoHandler());
218 pipeline->finalize();
223 int main(int argc, char** argv) {
224 gflags::ParseCommandLineFlags(&argc, &argv, true);
226 ClientBootstrap<EchoPipeline> client;
227 client.group(std::make_shared<folly::IOThreadPoolExecutor>(1));
228 client.pipelineFactory(std::make_shared<EchoPipelineFactory>());
229 auto pipeline = client.connect(SocketAddress(FLAGS_host, FLAGS_port)).get();
234 std::getline(std::cin, line);
239 pipeline->write(line + "\r\n").get();
245 } catch (const std::exception& e) {
246 std::cout << exceptionStr(e) << std::endl;
253 It reads input from stdin in a loop and writes it to the pipeline and it blocks until the response is processed. It blocks by calling .get() from the returned future.
257 This quick tutorial has shown how to quickly write a basic service in modern C++ using Wangle. You should now know the fundamentals of Wangle and it should give you confidence to write your own service in C++. It is strongly recommend to understand the Service abstraction once you are comfortable with using the Pipeline as you can build sophisticated servers with it.