proxygen
proxygen/wangle/tutorial.md
Go to the documentation of this file.
1 # Wangle Tutorial
2 
3 ## Introduction
4 
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.
6 
7 ## What is Wangle?
8 
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.
10 
11 ## What is a Pipeline?
12 
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.
14 
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.
16 
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.
18 
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.
20 
21 ## Echo Server
22 
23 Now onto writing our first service with Wangle: the Echo Server.
24 
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.
26 
27 ``` cpp
28 // the main logic of our echo server; receives a string and writes it straight
29 // back
30 class EchoHandler : public HandlerAdapter<std::string> {
31  public:
32  virtual void read(Context* ctx, std::string msg) override {
33  std::cout << "handling " << msg << std::endl;
34  write(ctx, msg + "\r\n");
35  }
36 };
37 ```
38 
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.
40 
41 ```cpp
42 // where we define the chain of handlers for each messeage received
43 class EchoPipelineFactory : public PipelineFactory<EchoPipeline> {
44  public:
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());
51  pipeline->finalize();
52  return pipeline;
53  }
54  };
55 ```
56 
57 It is **very** important to be strict in the order of insertion as they are ordered by insertion. The pipeline has 4 handlers:
58 
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
65  - **StringCodec**
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.
68  - **EchoHandler**
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.
71 
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.
73 
74 ```cpp
75 #include <gflags/gflags.h>
76 
77 #include <wangle/bootstrap/ServerBootstrap.h>
78 #include <wangle/channel/AsyncSocketHandler.h>
79 #include <wangle/codec/LineBasedFrameDecoder.h>
80 #include <wangle/codec/StringCodec.h>
81 
82 using namespace folly;
83 using namespace wangle;
84 
85 DEFINE_int32(port, 8080, "echo server port");
86 
87 typedef Pipeline<IOBufQueue&, std::string> EchoPipeline;
88 
89 // the main logic of our echo server; receives a string and writes it straight
90 // back
91 class EchoHandler : public HandlerAdapter<std::string> {
92  public:
93  virtual void read(Context* ctx, std::string msg) override {
94  std::cout << "handling " << msg << std::endl;
95  write(ctx, msg + "\r\n");
96  }
97 };
98 
99 // where we define the chain of handlers for each message received
100 class EchoPipelineFactory : public PipelineFactory<EchoPipeline> {
101  public:
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();
109  return pipeline;
110  }
111 };
112 
113 int main(int argc, char** argv) {
114  gflags::ParseCommandLineFlags(&argc, &argv, true);
115 
116  ServerBootstrap<EchoPipeline> server;
117  server.childPipeline(std::make_shared<EchoPipelineFactory>());
118  server.bind(FLAGS_port);
119  server.waitForStop();
120 
121  return 0;
122 }
123 ```
124 
125 We've written an asynchronous C++ server in under 48 LOC.
126 
127 ## Echo Client
128 
129 The code for the echo client is very similar to the Echo Server. Here is the main echo handler.
130 
131 ```cpp
132 // the handler for receiving messages back from the server
133 class EchoHandler : public HandlerAdapter<std::string> {
134  public:
135  virtual void read(Context* ctx, std::string msg) override {
136  std::cout << "received back: " << msg;
137  }
138  virtual void readException(Context* ctx, exception_wrapper e) override {
139  std::cout << exceptionStr(e) << std::endl;
140  close(ctx);
141  }
142  virtual void readEOF(Context* ctx) override {
143  std::cout << "EOF received :(" << std::endl;
144  close(ctx);
145  }
146 };
147 ```
148 
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.
150 
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.
152 
153 ```cpp
154 // chains the handlers together to define the response pipeline
155 class EchoPipelineFactory : public PipelineFactory<EchoPipeline> {
156  public:
157  EchoPipeline::Ptr newPipeline(std::shared_ptr<AsyncTransportWrapper> sock) {
158  auto pipeline = EchoPipeline::create();
159  pipeline->addBack(AsyncSocketHandler(sock));
160  pipeline->addBack(
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();
166  return pipeline;
167  }
168 };
169 ```
170 
171 What does it looks like when it is all put together for the client?
172 
173 ```cpp
174 #include <gflags/gflags.h>
175 #include <iostream>
176 
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>
182 
183 using namespace folly;
184 using namespace wangle;
185 
186 DEFINE_int32(port, 8080, "echo server port");
187 DEFINE_string(host, "::1", "echo server address");
188 
189 typedef Pipeline<folly::IOBufQueue&, std::string> EchoPipeline;
190 
191 // the handler for receiving messages back from the server
192 class EchoHandler : public HandlerAdapter<std::string> {
193  public:
194  virtual void read(Context* ctx, std::string msg) override {
195  std::cout << "received back: " << msg;
196  }
197  virtual void readException(Context* ctx, exception_wrapper e) override {
198  std::cout << exceptionStr(e) << std::endl;
199  close(ctx);
200  }
201  virtual void readEOF(Context* ctx) override {
202  std::cout << "EOF received :(" << std::endl;
203  close(ctx);
204  }
205 };
206 
207 // chains the handlers together to define the response pipeline
208 class EchoPipelineFactory : public PipelineFactory<EchoPipeline> {
209  public:
210  EchoPipeline::Ptr newPipeline(std::shared_ptr<AsyncTransportWrapper> sock) {
211  auto pipeline = EchoPipeline::create();
212  pipeline->addBack(AsyncSocketHandler(sock));
213  pipeline->addBack(
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();
219  return pipeline;
220  }
221 };
222 
223 int main(int argc, char** argv) {
224  gflags::ParseCommandLineFlags(&argc, &argv, true);
225 
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();
230 
231  try {
232  while (true) {
233  std::string line;
234  std::getline(std::cin, line);
235  if (line == "") {
236  break;
237  }
238 
239  pipeline->write(line + "\r\n").get();
240  if (line == "bye") {
241  pipeline->close();
242  break;
243  }
244  }
245  } catch (const std::exception& e) {
246  std::cout << exceptionStr(e) << std::endl;
247  }
248 
249  return 0;
250 }
251 ```
252 
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.
254 
255 ## Summary
256 
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.