Skip to content

Commit 070c945

Browse files
authored
add an HTTP server example (#35)
* install .a as well as .so * now the headers disappeared... let's try this * I learned how to build my own library. * so far, a traced HTTP server * mention dd-trace-cpp in error messages * some comments * extend httplib to facilitate tracing * traced HTTP server is working, but not yet complete * clang-format * give the hasher example CMake target a more specific name * add a /sleep endpoint, and refactor/reformat * provisional propagation * simplify /sleep * It works! * put a proxy in front of the C++ server * document the example server source code * document the database service * consider 400 an error in the span, which really I shouldn't, but it's the only way to get the error message into the trace * convenience script for running the HTTP server example * start the example README * add demo to readme * add attribution for picosha2 and cpp-httplib * pull the next release
1 parent 6fb4e61 commit 070c945

25 files changed

+9333
-9
lines changed

CMakeLists.txt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,10 +207,6 @@ include_directories(${CMAKE_BINARY_DIR}/include)
207207
find_package(Threads REQUIRED)
208208
target_link_libraries(dd_trace_cpp-objects PUBLIC ${CMAKE_BINARY_DIR}/lib/libcurl.a PUBLIC Threads::Threads ${COVERAGE_LIBRARIES})
209209

210-
# When installing, install the library and its public headers.
211-
install(TARGETS dd_trace_cpp-objects
212-
FILE_SET public_headers)
213-
214210
# Produce both shared and static versions of the library.
215211
add_library(dd_trace_cpp-shared SHARED $<TARGET_OBJECTS:dd_trace_cpp-objects>)
216212
set_target_properties(dd_trace_cpp-shared PROPERTIES OUTPUT_NAME "dd_trace_cpp")
@@ -222,6 +218,11 @@ set_target_properties(dd_trace_cpp-static PROPERTIES OUTPUT_NAME "dd_trace_cpp")
222218
add_dependencies(dd_trace_cpp-static dd_trace_cpp-objects)
223219
target_link_libraries(dd_trace_cpp-static dd_trace_cpp-objects)
224220

221+
# When installing, install the static library, the shared library, and the
222+
# public headers.
223+
install(TARGETS dd_trace_cpp-static dd_trace_cpp-shared dd_trace_cpp-objects
224+
FILE_SET public_headers)
225+
225226
if(BUILD_TESTING)
226227
add_subdirectory(test)
227228
endif()

LICENSE-3rdparty.csv

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
Component,Origin,License,Copyright
22
vendored,nlohmann/json,MIT,Copyright (c) 2013-2022 Niels Lohmann <http://nlohmann.me>
3-
vendored,Catch2,Boost,Copyright (c) 2022 Two Blue Cubes Ltd. All rights reserved.
3+
vendored,Catch2,Boost,Copyright (c) 2022 Two Blue Cubes Ltd. All rights reserved.
4+
vendored,picosha2,MIT,Copyright (C) 2017 okdshin
5+
vendored,cpp-httplib,MIT,Copyright (c) 2023 Yuji Hirose. All rights reserved.

bin/http-server-example

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/bin/sh
2+
3+
set -e
4+
5+
if [ "$DD_API_KEY" = '' ]; then
6+
>&2 echo "The DD_API_KEY environment variable must be set to a Datadog API key."
7+
exit 1
8+
fi
9+
10+
REPO=$(dirname "$0")/..
11+
cd "$REPO"
12+
13+
cd examples/http-server
14+
docker compose up --build --abort-on-container-exit

examples/hasher/CMakeLists.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
add_executable(example hasher.cpp)
1+
add_executable(hasher-example hasher.cpp)
2+
set_target_properties(hasher-example PROPERTIES OUTPUT_NAME example)
23

3-
target_link_libraries(example dd_trace_cpp-static)
4+
target_link_libraries(hasher-example dd_trace_cpp-static)

examples/http-server/README.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
Example HTTP Server
2+
===================
3+
This directory contains a [docker compose][1] setup for a system consisting of
4+
three HTTP services and a [Datadog Agent][2].
5+
6+
The "server" service is a note-taking app written in C++. Its source code is is
7+
in the [server/](server/) directory. See [server/server.cpp](server/server.cpp).
8+
It uses a modified version of [cpp-httplib][3] to accept and make HTTP requests.
9+
10+
A reverse proxy sits in front of the server, and the reverse proxy is exposed to
11+
the host by binding to port 8000.
12+
13+
![diagram of services](diagrams/services.svg)
14+
15+
The note-taking app provides the following endpoints:
16+
17+
- _GET /notes_
18+
- Return `application/json` containing an array of notes, where each notes
19+
is an array containing the note's creation time and body text.
20+
For example, `[["2023-05-12 16:18:37","here is another note"],["2023-05-12 15:06:10","this is my note"]]`.
21+
- _POST /notes_
22+
- Insert the request body into the database as a new note.
23+
- _GET /sleep?seconds=[duration]_
24+
- Wait for the specified `[duration]` number of seconds before responding.
25+
26+
The [bin/http-server-example](../../bin/http-server-example) script builds the
27+
three services, starts them, and begins tailing their logs. The "server" build
28+
uses a GitHub release of dd-trace-cpp, rather than using the current source
29+
tree. `bin/http-server-example` requires a Datadog API key via the `DD_API_KEY`
30+
environment variable.
31+
32+
Once the services are started, requests can be made to `127.0.0.1:8000`. Traces
33+
will appear in the Datadog UI. The `docker compose` services have the following
34+
Datadog service names:
35+
36+
| docker compose service name | Datadog service name |
37+
| --------------------------- | -------------------- |
38+
| proxy | dd-trace-cpp-http-server-example-proxy |
39+
| server | dd-trace-cpp-http-server-example-server |
40+
| database | dd-trace-cpp-http-server-example-database |
41+
42+
Example Usage
43+
-------------
44+
In one command shell, run `bin/http-server-example` to bring up the services.
45+
46+
![screenshot of command mentioned above](diagrams/docker-compose.png)
47+
48+
Then, in another command shell, use `curl` to read and write some notes.
49+
50+
![screenshot of curl being used](diagrams/curl.png)
51+
52+
Finally, open the Datadog UI in a web browser and navigate to `APM > Traces`.
53+
Search for service names matching `dd-trace-cpp-http-server-example-*`.
54+
55+
![screenshot of search results](diagrams/traces.png)
56+
57+
Click one of the results to display a flame graph of the associated trace.
58+
59+
![screenshot of flame graph](diagrams/flame-graph.png)
60+
61+
At the top is the Node.js proxy that we called using `curl`. Below that is the
62+
C++ server to which the proxy forwarded our request. Below that is the
63+
Python database service, including a span indicating its use of SQLite.
64+
65+
[1]: https://docs.docker.com/compose/
66+
[2]: https://docs.datadoghq.com/agent/
67+
[3]: https://github.com/yhirose/cpp-httplib
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from alpine:3.17
2+
3+
run apk update && \
4+
apk add python3 py3-pip && \
5+
pip install ddtrace flask gevent
6+
7+
copy database.py /usr/local/bin/database.py
8+
9+
cmd ["ddtrace-run", "python", "/usr/local/bin/database.py"]
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"""database - a thin wrapper around a sqlite3 database
2+
3+
This is an HTTP server that exposes a SQLite 3 database via the following
4+
endpoints:
5+
6+
GET /query?sql=<sql>
7+
Execute the <sql> against the database in read-only mode. Return
8+
application/json containing an array of the resulting rows, where
9+
each row is an array.
10+
11+
If an error occurs, return a text/plain description of the error.
12+
13+
GET /execute?sql=<sql>
14+
Execute the <sql> against the database in read-write mode.
15+
On success, return status 200 with an empty body.
16+
17+
If an error occurs, return a text/plain description of the error.
18+
19+
The database initially contains the following table:
20+
21+
create table Note(
22+
AddedWhen text,
23+
Body text);
24+
"""
25+
26+
import flask
27+
import gevent
28+
from gevent.pywsgi import WSGIServer
29+
import signal
30+
import sqlite3
31+
import sys
32+
33+
DB_FILE_PATH = '/tmp/database.sqlite'
34+
35+
app = flask.Flask(__name__)
36+
37+
38+
@app.route('/')
39+
def hello():
40+
return "Hello, World!"
41+
42+
43+
@app.route('/query')
44+
def query():
45+
request = flask.request
46+
sql = request.args.get('sql')
47+
if sql is None:
48+
return ('"sql" query parameter is required.', 400)
49+
50+
with sqlite3.connect(f'file:{DB_FILE_PATH}?mode=ro') as db:
51+
try:
52+
return list(db.execute(sql))
53+
except Exception as error:
54+
return str(error) + '\n', 400
55+
56+
57+
@app.route('/execute')
58+
def execute():
59+
request = flask.request
60+
sql = request.args.get('sql')
61+
if sql is None:
62+
return ('"sql" query parameter is required.', 400)
63+
64+
with sqlite3.connect(DB_FILE_PATH) as db:
65+
try:
66+
db.execute(sql)
67+
return ''
68+
except Exception as error:
69+
return str(error) + '\n', 400
70+
71+
72+
if __name__ == '__main__':
73+
gevent.signal_handler(signal.SIGTERM, sys.exit)
74+
75+
with sqlite3.connect(DB_FILE_PATH) as db:
76+
db.execute('''
77+
create table if not exists Note(
78+
AddedWhen text,
79+
Body text);
80+
''')
81+
82+
http_server = WSGIServer(('', 80), app)
83+
http_server.serve_forever()
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.PHONY: all
2+
all: services.svg
3+
4+
%.svg: %.dot Makefile
5+
dot -Tsvg -o "$@" "$<"
229 KB
Loading
509 KB
Loading

0 commit comments

Comments
 (0)