p4-web
EECS 280 Project 4: Web
Due Tuesday, April 6, 2021, 8400pm Change Log
2021-03-24 Fixed bug in index.html where it showed 1 person on queue when all people were removed. This is a non-critical fix. If you¡¯d like the demo to work correctly, updated your index.html from the starter files.
2021-03-22 Fix Project UID in Setup – API section.
In this project, you will build a small web server for an office hours queue. The server will read and write an HTTP subset on stdin and stdout. When you¡¯re done, you¡¯ll have a working web application accessible through your browser.
Project roadmap
This is a big picture view of how to do this project.
Set up your IDE with the starter code
See the Setup section to get your visual debugger configured with the starter files.
Review the code structure
The code structure is templated and object-oriented, with a class representing a doubly-linked list. Additionally, the list will be used as a queue.
Implement the main application
Write and test a function in that runs an office hours queue web site. Use the standard library list, std::list . See the Office hours queue specification section.
Test and implement List
Implement and test a List ADT that works like std::list from the STL. See the Linked list
specification section.
Submit
Submit the following files to the autograder.
List.h
List_tests.cpp
api.cpp
Introduction
When you browse to a web site like our EECS 280 office hours queue http://eecsoh.org, your computer makes a request and a server returns a response.
Simple web pages
Your web browser makes a request when you visit a
main()
api.cpp
page. First, it connects to the then requests the
is a shortcut for
server, page (¡°no filename¡±
).
eecsoh.org
/index.html
index.html
GET / HTTP/1.1
The eecsoh.org server responds with plain text in HTML format. Your browser renders the HTML, adding colors, formatting and images.
HTTP/1.1 200 OK
EECS Office Hours
…
HTTP
HTTP is the protocol that describes what requests and responses should look like. Both are plain text sent from one computer to another computer through the internet. Let¡¯s take a second look at the previous example in more detail.
The request contains an action ( ), a path
( ), a version ( headers (
pairs separated by a colon.
) and some
). Headers are key/value
HTTP/1.1
Host: localhost
GET /eecsoh/ HTTP/1.1 Host: localhost
The response contains a version ( status code ( ), status description ( headers ( and
… ), and a body (
), a
), some
).
GET
/eecsoh/
HTTP/1.1
200
OK
Content-Type …
Content-Length
…
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8 Content-Length: 3316
Web 2.0 applications
Web 2.0 applications like the EECS 280 office hours queue interact with the user. Let¡¯s take a look at what happens when you click the ¡°Sign Up¡± button.
First, the client¡¯s web browser sends an HTTP request to the server. The request might look like this. Notice that the request includes a body with the information entered by the client. The information is in a machine-readable format called JSON.
8
POST /api/queue/tail/ HTTP/1.1
Host: localhost
Content-Type: application/json; charset=utf- Content-Length: 59
{
“uniqname”: “awdeorio”, “location”: “2705 BBB”
}
Next, the server receives the request sent by the client. The server acts on the request.
EECS Office Hours
…
X. Deserialize the JSON data, converting it into a data structure
Y. Modify an internal data structure, possibly a list
Z. Createaresponsedatastructure
[. Serialize the response data structure, converting it to JSON
\. Sendtheresponsetotheclient
The response to the client might look like this.
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf- Content-Length: 78
{
“location”: “2705 BBB”, “position”: 1, “uniqname”: “awdeorio”
}
Finally, the client receives the response and updates the web page, showing the up-to-date queue in this example.
A server that responds to requests with data instead of HTML is called a REST API (REpresentational State Transfer). REST APIs return data in a machine- readable format like JSON.
Appendix A: Working with JSON provides many more details about the JSON format.
Setup
We¡¯ll remind you which parts of the EECS 280 Setup Mega Tutorial you need to look back at to get started on this project.
8
Group registration
Please register your partnership (or working alone) on the Autograder.
Project folder
Create a folder for this project (instructions). Your folder location might be different.
Version control
Set up version control using the Version control tutorial. If you¡¯ve used version control before on your computer, you¡¯ll probably want to start with the Create a local repository section.
Be sure to check out the Version control for a team tutorial.
After you¡¯re done, you should have a local repository with a ¡°clean¡± status and your local repository should be connected to a remote GitLab repository.
r’
io io
$ pwd
/Users/awdeorio/src/eecs280/p4-web
$ pwd
/Users/awdeorio/src/eecs280/p4-web
$ git status
On branch master
Your branch is up-to-date with ‘origin/maste
nothing to commit, working tree clean
$ git remote -v
origin https://gitlab.eecs.umich.edu/awdeor
origin https://gitlab.eecs.umich.edu/awdeor
.
/ /
You should have a file (instructions).
$ pwd
/Users/awdeorio/src/eecs280/p4-web
$ head .gitignore
# This is a sample .gitignore file that’s us …
Starter files
ef
Download the starter files and unpack them. These instructions are adapted from the Setup Tutorial Starter files section.
$ pwd
/Users/awdeorio/src/eecs280/p4-web
$ wget https://eecs280staff.github.io/p4-web
$ tar -xvzf starter-files.tar.gz
$ ls
starter-files starter-files.tar.gz
Move the starter files from
present working directory ( ) and clean up the old directory and tarball.
/s
Rename each .cpp.starter file to a .cpp file.
starter-files/
to your
.
$ mv starter-files/* .
$ rm -rf starter-files/ starter-files.tar.gz $ ls
List.h.starter public_error01.in List_compile_check.cpp public_error01.out.c List_public_test.cpp server.py
List_tests.cpp
Makefile
index.html
json.hpp
master awdeorio@manco p4-web-awdeorio
test01.in
test01.out.correct
test02.in
test02.out.correct
or
.gitignore
u
t
r
Create these new files.
$ touch api.cpp
Your project directory should look like this.
$ ls
List.h public_error01.in
List_compile_check.cpp public_error01.out.c
List_public_test.cpp server.py
List_tests.cpp
Makefile
index.html
json.hpp
test01.in
test01.out.correct
test02.in
test02.out.correct
You can find a complete description of the starter files later in this spec.
Create a project in your IDE
Create a project in your IDE (visual debugger). Later, we¡¯ll compile and debug.
VS Code
or
If you¡¯ve already used VS Code on your computer before, all you need to do is start VS Code from your project directory. Otherwise, start the tutorial at the beginning. Stop after you¡¯ve completed the Install the C/C++ extension section.
$ pwd
/Users/awdeorio/src/eecs280/p4-web
$ code .
$ mv List.h.starter List.h
r
Visual studio
If you¡¯ve already used Visual Studio on your computer before, start the tutorial at the Create a project section, otherwise start at the beginning. Stop after you¡¯ve completed the Add existing files section.
Xcode
If you¡¯ve already used Xcode on your computer before, start the tutorial at the Create a project section, otherwise start at the beginning. Stop after you¡¯ve completed the Add existing files section.
Compile
Next, we¡¯ll get all our code to compile by adding function stubs.
List
Edit List.h , adding a function stub for every function prototype at the top of List.h . Recall the example of function stubs in Project 1¡¯s stats.cpp .
Pro-tip: Start by copy-pasting each function prototype from the class declaration at the top of
List.h to the bottom of the same file.
Here is an example of a function stub to get you
started.
template
bool List
assert(false);
}
Now you should be able to compile and run the List unit tests. The public tests will fail until you implement
the functions. The test you write ( ) will pass because the starter file only contains
ASSERT_TRUE(true) .
$ make List_public_test.exe
c++ -Wall -Werror -pedantic –std=c++11 -g L
$ ./List_public_test.exe
Running test: test_list_default_ctor
Assertion failed: (false), function empty, f
$ make List_tests.exe
c++ -Wall -Werror -pedantic –std=c++11 -g L
$ ./List_tests.exe
Running test: test_stub
PASS
*** Results ***
** Test case “test_stub”: PASS
*** Summary ***
Out of 1 tests run:
0 failure(s), 0 error(s)
At this point, we haven¡¯t written the List Iterator, so List_compile_check.exe won¡¯t compile. You¡¯ll
need to take a look at the lecture about iterators and write your own tests. After you do, use the provided compile check like this:
API
Edit to print a message, similar to Project 1¡¯s .
Make sure that the Project UID is in the comments at the top.
is
il is
$ make List_compile_check.exe
api.cpp
main.cpp
// Project UID c1f28c309e55405daf00c565d57ff
9a
List_tests.cpp
t
e t
d
You should be able to compile and run your main function.
$ make api.exe
c++ -Wall -Werror -pedantic –std=c++11 -g a
$ ./api.exe
hello from main!
Everything
pi
You can check if everything compiles all at once by running make -j , which compiles everything in parallel. The tests will fail until you¡¯ve implemented the functions. Also, List_compile_check.exe won¡¯t compile until you write the List Iterator.
Commit and submit
Now that your code compiles with function stubs, it¡¯s a great time to make a Git commit and make your first autograder submission.
Submit the code you have to the autograder. It¡¯s best to find out early if there¡¯s a mismatch between the autograder and your local development environment.
$ make clean
rm -vrf *.o *.exe *.gch *.dSYM *.stackdump *
$ make -j
.o
Make a git commit.
$ git status
$ git add .
$ git commit -m “Starter files with function
$ git push
s
.
u
t
Debug
Configure your visual debugger to compile and run the api.exe with the test01.in system test.
Here¡¯s how to run test01 from the command line. We see that the actual output ( test01.out ) says ¡°hello from main!¡±, but the correct output provided with the starter files ( test01.out.correct ) says ¡°HTTP/1.1 …¡±.
$ make api.exe
c++ -Wall -Werror -pedantic –std=c++11 -g a $ ./api.exe < test01.in > test01.out
$ ./api.exe < test01.in > test01.out
master awdeorio@manco p4-web-awdeorio
$ diff test01.out test01.out.correct
1c1,9
< hello from main!
---
> HTTP/1.1 200 OK
> Content-Type: application/json; charset=ut > Content-Length: 160
>
>{
> “queue_head_url”: “http://localhost/qu
> “queue_list_url”: “http://localhost/qu
> “queue_tail_url”: “http://localhost/qu >}
VS Code
Follow the VS Code Run instructions and configure it to run . Don¡¯t forget to change
to api.exe in launch.json . Edit the setting for input redirection so that
test01.in is the input file.
Finally, follow the Debug instructions to run your program with a breakpoint.
pi
f-
eu eu eu
api.exe
stats_tests.exe
.
8
e e e
Visual Studio
First, make sure the correct files are included in the build. You can figure out the right files by compiling at the command line. In this example, you can see that we¡¯ll need only api.cpp . All other files should be excluded from the build.
$ make api.exe
c++ -Wall -Werror -pedantic –std=c++11 -g a
Edit the setting for input redirection so that test01.in is the input file.
pi
Now you should be able to run your main program in api.cpp from inside Visual Studio (instructions).
Finally, follow the Debug instructions to run your program with a breakpoint.
Xcode
First, make sure the correct files are included in the Xcode compile sources. You can figure out the right files by compiling at the command line. In this example, you can see that we¡¯ll need only api.cpp . All other files should be excluded from the build.
$ make api.exe
c++ -Wall -Werror -pedantic –std=c++11 -g a
Next, change the settings for running your program (instructions).
Follow the instructions for input redirection so that test01.in is the input file.
Finally, follow the Debug instructions to run your program with a breakpoint.
pi
.
.
Configure Address Sanitizer
The Address Sanitizer is very good at finding memory errors, including going off the end of an array or vector or making a mistake with a pointer. Use the Address Sanitizer Quick Start.
Office hours queue specification
The top-level application is an office hours queue REST API that reads requests from stdin ( cin ) and writes responses to stdout ( cout ). Requests and responses are formatted using a simplified subset of real HTTP.
A queue contains items stored in first-in-first-out order: the first item to be added is also the first one to be removed. Queues are commonly implemented using a linked list. A linked list allows insertion and removal at both ends, allowing items to be added at one end and removed at the other. This is in contrast to a vector, which only allows insertion and removal at one end.
Use the standard library list so you can get started on this project right away. Later, you¡¯ll implement your own linked list that works just like the STL.
The REST API will implement the requests summarized in this table. The following sections provide more detail.
Request
Description
GET /api/
Read routes
Read all queue positions
GET /api/queue/
GET
Read first queue position
/api/queue/head/
POST
Create last queue position
/api/queue/tail/
DELETE
Code structure
Use a linked list containing a or class to store your queue. Don¡¯t use objects to store your queue or the data in your queue.
Here¡¯s an outline of how to structure your solution.
X. Read a request with cin
To read data, create a temporary json object and use cin (Reading JSON from a stream)
Y. Read or write the list data structure
Z. Write a response with cout
To write data, create a temporary json object and use cout (Writing JSON to a stream)
Your code should be structured in such a way that your program will return 0 if it fails to read the beginning of a request from cin (i.e. it fails to read one of ¡°GET /api/¡±, ¡°POST /api/queue/tail/¡±, etc. because of some error, including end of file). NOTE: Your code should not return from main if it encounters an error as described in Error handling.
Delete first queue position
/api/queue/head/
json
struct
Pro-tip: Here¡¯s how the instructors started their solution.
#include
class OHQueue { public:
private:
struct Student {
};
std::list
Sample browser session
The following is an example of a browser session that adds three people to an empty queue and then retrieves the full queue.
The browser starts by sending a POST request to the /api/queue/tail path to add a student. The
request body includes the student¡¯s uniqname and location.
8
POST /api/queue/tail/ HTTP/1.1
Host: localhost
Content-Type: application/json; charset=utf- Content-Length: 58
{
“uniqname”: “awdeorio”, “location”: “Table 3”
}
The server returns the following response, indicating success:
HTTP/1.1 201 Created
The browser sends a second POST request to add another student:
POST /api/queue/tail/ HTTP/1.1
Host: localhost
Content-Type: application/json; charset=utf- Content-Length: 57
{
“uniqname”: “akamil”, “location”: “Table 15”
}
The server responds:
8
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf- Content-Length: 76
{
“location”: “Table 15”, “position”: 2, “uniqname”: “akamil”
}
The browser adds one more student to the queue:
8
Content-Type: application/json; charset=utf-
Content-Length: 77
{
“location”: “Table 3”, “position”: 1, “uniqname”: “awdeorio”
}
POST /api/queue/tail/ HTTP/1.1
Host: localhost
Content-Type: application/json; charset=utf- Content-Length: 75
8
8
The server response:
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf- Content-Length: 94
{
“location”: “Desks behind bookshelves”, “position”: 3,
“uniqname”: “jklooste”
}
The browser now sends a GET request to the /api/queue/ path to obtain the entire queue:
8
GET /api/queue/ HTTP/1.1
Host: localhost
Content-Type: application/json; charset=utf- Content-Length: 0
The server responds with the contents of the queue in order:
8
{
“uniqname”: “jklooste”,
“location”: “Desks behind bookshelves”
}
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf- Content-Length: 412
{
“count”: 3,
“results”: [ {
“location”: “Table 3”, “position”: 1, “uniqname”: “awdeorio”
},
8
The requests for this example are in the file , and the responses are in
.
test02.in
test02.out.correct
Request format
l
Every request has the same format. The only parts that change are the method ( GET in this example), the path ( /api/ in this example), the content length ( 0 here) and the body (empty here).
The content length in a request is the number of bytes in the body. Two newlines between the headers and the body are not included in the content length. Each body is followed a newline, which is included in the content length.
In this example, the two newlines separating the headers and the body are present, but the body is empty. That is why you see a blank line at the end and
Content-Length: 0 .
GET /api/ HTTP/1.1
Host: localhost
Content-Type: application/json; charset=utf- Content-Length: 0
8
{
“location”: “Table 15”, “position”: 2, “uniqname”: “akamil”
}, {
} ]
}
“location”: “Desks behind booksh “position”: 3,
“uniqname”: “jklooste”
ev
Response format
Every response has the same format. The only parts that change are the response code ( 200 in this example), the content length ( 160 ) and the body. The body is everything inside the curly braces { … } followed by a trailing newline.
8
e/ e/ e/
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf- Content-Length: 160
{
“queue_head_url”: “http://localhost/queu “queue_list_url”: “http://localhost/queu “queue_tail_url”: “http://localhost/queu
}
Your implementation must order key-value pairs alphabetically by key. Use the process in Writing JSON to a stream to ensure that the ordering is correct.
The content length in a response is the number of bytes in the body. Two newlines between the headers and the body are not included in the content length. Each body is followed a newline, which is included in the content length.
Error handling
Your implementation may assume that requests are properly formatted, and that the HTTP method is one of GET , DELETE , or POST . However, you must handle the following errors:
HTTP path is not valid. The path must exactly match one of , ,
, or , including the slashes.
/api/
/api/queue/
/api/queue/head/
/api/queue/tail/
h ” t
HTTP method is not appropriate for the path. For example, POST /api/ .
If one of the errors above occurs, read the remainder of the request, including any headers or body. Then, return the following response after reading the entire request. Note that there is a blank line after Content- Length: 0 .
8
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf- Content-Length: 0
The Content-Length in the HTTP header tells you whether or not there is a body. If the length is nonzero, a body follows. You may assume that the
Content-Length of a request is correct. GET /api/
The /api/ route accepts a GET request and returns a list of URLs supported by this REST API. It always returns the same data. See the examples in Request format and Response format for the input and output for this path.
Run the unit test.
POST /api/queue/tail/
The /api/queue/tail/ route accepts a POST request and creates one new person on the queue. As a simplification, we do not check if a person is already
$ make api.exe
$ ./api.exe < test01.in > test01.out $ diff test01.out test01.out.correct
on the queue, thus the same uniqname may appear multiple times.
Example request
POST /api/queue/tail/ HTTP/1.1
Host: localhost
Content-Type: application/json; charset=utf- Content-Length: 58
{
“uniqname”: “jackgood”, “location”: “Table 5”
}
Example response
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf- Content-Length: 77
{
“location”: “Table 5”, “position”: 1, “uniqname”: “jackgood”
}
Run the unit test.
GET /api/queue/head/
8
8
$ make api.exe
$ ./api.exe < test04.in > test04.out $ diff test04.out test04.out.correct
The /api/queue/head route accepts a GET request and returns the person at the head of the queue. Fields are in the order shown by the example, and the person at the head of the queue always has position
1. If the queue is empty, return a error. Example request
GET /api/queue/head/ HTTP/1.1
Host: localhost
Content-Type: application/json; charset=utf- Content-Length: 0
Example response
8
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf- Content-Length: 77
{
“location”: “Table 3”, “position”: 1, “uniqname”: “awdeorio”
}
Run the unit test.
GET /api/queue/
The /api/queue/ route accepts a GET request and returns a list of everyone on the queue, including
location , position and uniqname in that order. The list is ordered by position, which always starts with 1 for the person currently at the head of the queue.
8
$ make api.exe
$ ./api.exe < test03.in > test03.out $ diff test03.out test03.out.correct
Example request
400
Example response
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf- Content-Length: 412
{
“count”: 3,
“results”: [ {
“location”: “Table 3”, “position”: 1, “uniqname”: “awdeorio”
}, {
}, {
} ]
}
“location”: “Table 15”, “position”: 2, “uniqname”: “akamil”
“location”: “Desks behind booksh “position”: 3,
“uniqname”: “jklooste”
If the queue is empty, the response should be:
8
el
GET /api/queue/ HTTP/1.1
Host: localhost
Content-Type: application/json; charset=utf- Content-Length: 0
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf- Content-Length: 40
{
“count”: 0,
“results”: null
8
8
v
Run the unit test.
$ make api.exe
$ ./api.exe < test02.in > test02.out $ diff test02.out test02.out.correct
DELETE /api/queue/head/
The /api/queue/head/ route accepts a DELETE request and removes the person at the head of the queue.
Example request
DELETE /api/queue/head/ HTTP/1.1
Host: localhost
Content-Type: application/json; charset=utf- Content-Length: 0
Example response
8
HTTP/1.1 204 No Content
Content-Type: application/json; charset=utf- Content-Length: 0
8
If the queue is empty, return a 400 error. Run the unit test.
$ make api.exe
$ ./api.exe < test05.in > test05.out $ diff test05.out test05.out.correct
Real web server
}
Once you have a working solution for the office hours queue API as specified, you simultaneously have a working back-end for a real office hours queue web server! You can now run this office hours queue locally and interact with it through your web browser by following the directions in Appendix B: Real web server!
Linked list specification
Implement your doubly linked list in List.h . List.h.starter provides prototypes for each
function. Because List is a templated container, function implementations go in List.h . There is no
List.cpp .
While the List from lecture was singly linked, this List is doubly linked. This List also contains an
iterator interface.
Do not modify the public interface of the List class. Implement a doubly-linked list. No arrays or vectors, etc. Manage memory allocation so that there are no memory leaks.
Compile and run the provided compile check and List tests.
Unit tests
You will write and submit tests for your doubly linked list. Your test cases must use the provided unit test framework.
$ make List_compile_check.exe
$ make List_public_test.exe
$ ./List_public_test.exe
Unit tests should be small and run quickly. You may submit up to 50 TEST() items and the entire test suite must run in less than 5 seconds.
Compile and run your List tests.
We will autograde your List unit tests by running them against several buggy instructor solutions. If a test of yours fails for one of those implementations, that is considered a report of a bug in that implementation. This is called mutation testing.
X. Theautogradercompilesandrunsyourtest cases with a correct solution. Test cases that pass are considered valid. Tests that fail (i.e. falsely report a bug in the solution) are invalid. The autograder gives you feedback about which test cases are valid/invalid. Since unit tests should be small and run quickly, your whole test suite must finish running in less than 5 seconds.
Y. Wehaveasetofintentionallyincorrect implementations that contain bugs. You get points for each of these buggy implementations that your valid tests can catch.
Z. Howdoyoucatchthebugs?Wecompileandrun all of your valid test cases against each buggy implementation. If any of these test cases fail (i.e. report a bug), we consider that you have caught the bug and you earn the points for that bug.
Requirements and restrictions
$ make List_tests.exe
$ ./List_tests.exe
It is our goal for you to gain practice with good C++ code, classes, and dynamic memory.
DO
DO NOT
Modify .cpp files and
List.h
Modify other .h files
For List , make helper member functions private
Modify the public interface of List
Use these libraries:
, ,
,
,
Use other libraries
Use (and optionally
) in
Use
,
, or other containers in List.h
api.cpp
Assume that the compiler will find the library for you (some do, some don¡¯t)
#include a library to use its functions
Use C++ strings
Use C-strings
Send all output to standard out (AKA stdout) by using cout
Send any output to standard error (AKA stderr) by using
cerr
Pass large structs or classes by reference
Pass large structs or classes by value
Pass by const reference when appropriate
¡°I don¡¯t think I¡¯ll modify it …¡±
Use Valgrind to check
¡°It¡¯s probably fine…¡±
for memory errors
Check for undefined behavior using address sanitizer and other tools
Starter code
You can obtain the starter files by following the instructions in Setup.
List.h.starter
File(s)
¡°It runs fine on my machine!¡±
Description
Skeleton List class template header file without function implementations. Rename this file to
List.h and then add your function implementations.
Add your List unit tests to this file.
List_tests.cpp
A ¡°does my code compile¡± test for
List.h
List_compile_check.cpp
A very small test case for List.h .
List_public_test.cpp
test01.in
test01.out.correct
test02.in
test02.out.correct
Simple test cases for the server
test03.in
test03.out.correct
program.
test04.in
test04.out.correct
test05.in
test05.out.correct
A Makefile that has targets for compiling the published test cases and your own tests. Use
$ make test
to compile and run all tests.
Makefile
The library you must use to work with JSON. See Appendix A: Working with JSON for details.
json.hpp
The unit test framework you must use to write your test cases.
unit_test_framework.h
Python wrapper script for running the office hours queue server.
server.py
HTML for the office hours queue.
index.html
Appendix A: Working with
JSON
The starter code includes json.hpp , a library for working with JSON in C++. To use the library, place the following at the top of a .cpp file:
JSON format
The JSON format is a simple text-based standard for representing structured data. It consists of the following data types:
numbers, which can be integer or floating point strings
booleans (either true or false )
null , which represents an empty value
arrays, which are delimited by square brackets in
the JSON format
objects, which are collections of key-value pairs and are delimited by curly braces. The keys must be strings, but the values may be any data type, including arrays or other objects.
Constructing a JSON object
The json.hpp library defines the json type to represent a JSON value. The documentation has an example of constructing a json object in C++ code. We summarize the example here. Suppose we wanted to construct the following JSON object:
#include “json.hpp”
using nlohmann::json;
This object has the following key-value pairs:
the key “pi” has a value that is the number 3.141
“happy” has a value that is the boolean true “name” has a value that is the string “Niels” “nothing” has a null (empty) value
the value for “an_array” is an array of three elements
the value for “an_object” is an object with two of its own key-value pairs
We can create the JSON object in C++ as follows:
json j2;
j2[“pi”] = 3.141; j2[“happy”] = true; j2[“name”] = “Niels”; j2[“nothing”] = json(); j2[“an_array”] = {1, 0, 2}; j2[“an_object”] = {
{“currency”, “USD”},
{“value”, 42.99}
};
{
“pi”: 3.141,
“happy”: true,
“name”: “Niels”, “nothing”: null, “an_array”: [1, 0, 2], “an_object”: {
“currency”: “USD”,
“value”: 42.99 }
}
The value for a particular key can be read using the same subscript syntax:
cout << j2["pi"] << endl; // prints 3.1 cout << j2["answer"] << endl; // prints {"e j2["tau"] = 6.283; // inserts "t cout << j2["tau"] << endl; // prints 6.2
A JSON object can also be created with a C++ initializer list, as in:
j2["an_object"] = { {"currency", "USD"}, {"value", 42.99}
};
Each element of the C++ initializer list must be another initializer list, containing both a key and a value. In this example, the key is associated with the string , and the key
"value" is associated with the number 42.99 . The following creates the same object as j2 above
using initializer lists:
41 ve au 83
"USD"
"currency"
json j2 = {
{"pi", 3.141},
{"happy", true}, {"name", "Niels"}, {"nothing", nullptr}, {"an_array", {1, 0, 2}}, {"an_object", {
{"currency", "USD"},
{"value", 42.99}
}
} };
JSON arrays
r "
Build a JSON array using .
json output;
json j4 = { {"happy", true}, {"pi", 3.141}
};
output.push_back(j4);
json j5 = {
{"happy", true}, {"pi", 3.14159265359}
};
output.push_back(j5);
The resulting JSON is
[
{
"pi": 3.141 },
{
"happy": true,
"pi": 3.14159265359 }
]
"happy": true,
Reading JSON from a stream
JSON-formatted data can be read from a stream into a json object using operator>> :
Writing JSON to a stream
json j3; cin >> j3;
A json object can be inserted directly into a stream.
push_back()
However, it does not get ¡°pretty printed¡± with proper indentation. Instead, use the dump() member function to convert a json object into a string and then insert the string into a stream:
json j4 = { {“happy”, true}, {“pi”, 3.141}
};
string str2 = j4.dump(4) + “\n”; // dump wi cout << str2; // print th
This results in the following:
{
"happy": true,
"pi": 3.141 }
The key-value pairs are ordered alphabetically by key.
Compute the content length for a request or response from the length of the dumped string. Pitfall: the string should include a trailing newline before computing the length.
Appendix B: Real web server
The REST API you built works in a real network. We¡¯ve provided a Python wrapper script with the networking code.
th e
size_t content_length = str2.length();
First, make sure your API passes all the unit tests.
d
Build and start the server. You might need to install Python 3 with (macOS) or
(WSL or Linux).
In a web browser, navigate to http://localhost:8000/index.html. You should see a web page. A shortcut is http://localhost:8000.
Now try http://localhost:8000/api/. You should see JSON data.
Your browser is sending a GET request over the network. Let¡¯s try it using the command line using a second terminal.
e/ e/ e/
brew install python3
apt-get install python3
$ make api.exe
$ python3 server.py
Starting server on localhost:8000
$ curl localhost:8000/api/
{
"queue_head_url": "http://localhost/queu
"queue_list_url": "http://localhost/queu
"queue_tail_url": "http://localhost/queu
}
The server.py script listens for incoming network requests. If the client request path starts with /api , it copies the request to the stdin of api.exe and
copies the stdout of Otherwise,
the network (e.g.,
Visual Studio Note
back to the client. copies a file to the client over
).
server.py
api.exe
index.html
If you are working on Windows and use Visual Studio
$ make test-api
h " t
(not to be confused with Visual Studio Code), compile from the Ubuntu (WSL) terminal ( make
), just for this demo. This avoids a problem with Windows vs. Linux line endings when running
server.py .
Appendix C: What¡¯s in a
typename?
You saw the use of typename for declaring templates. When compiling your project, you may get the following kind of error:
am
api.exe
api.exe
./List_tests.cpp:94:8: error: missing 'typen
List
^~~~~~~
If you see an error message that talks about missing ¡®typename¡¯ prior to dependent type name, simply stick in the keyword ¡° typename ¡± before the type declaration. In the instance above, it would become:
The same thing would apply if you declared a loop variable. For example:
may need to become (if you get an error from the compiler):
typename List
for (List
for (typename List
Discussion of dependent types and why we have to
e
insert the keyword is beyond the scope of this course (the reason is quite subtle and deep). If you want to see some explanation, see this article:
http://pages.cs.wisc.edu/~driscoll/typename.html
Appendix D: Project 4 Coding Practices Checklist
The following are coding practices you should adhere to when implementing the project. Adhering to these guidelines will make your life easier and improve the staff¡¯s ability to help you in office hours. You do not have to submit this checklist.
General code quality:
Helper functions used if and where appropriate. Helper functions are designed to perform one meaningful task, not more
Lines are not too long
Descriptive variable and function names (i.e. int
radius instead of int x )
Effective, consistent, and readable line indentation
Code is not too deeply nested in loops and conditionals
Main function is reasonably short Avoids redundant use of this keyword
Test case quality:
Test cases are small and test one behavior each.
Test case names are descriptive, or test cases are commented with a short description of what they
typename
test.
Test cases are written using the unit testing framework.
Project-specific quality:
The big three are only implemented when required.