Challenge #2c: Parsing the Request Body
In this challenge, you’ll remove the remaining hardcoded value from HomePageController.postAsync()
. Instead of hard-coding the text to encode, you’ll parse the HTTP request body.
This challenge introduces the Configurable Responses pattern. You’ll use it to configure the HTTP request.
Instructions
1. Revise the "POST asks ROT-13 service to transform text"
test:
- Change the test to configure the request body to be
"text=hello%20world"
. This URL-encoded form data matches what real browsers will send. - Change the test’s assertion to check that
"hello world"
is sent to the ROT-13 service.
2. Revise HomePageController.postAsync()
:
- Parse the text field from the request body and send it to the ROT-13 service.
- Don’t worry about edge cases—just program the happy path.
Remember to commit your changes when you’re done.
API Documentation
const request = HttpServerRequest.createNull({ body });
Create an HttpServerRequest
instance with the provided body. Note that the parameter is an object—don’t forget the curly braces!
- body
(string)
- the request body - returns request
(WwwConfig)
- the request
const form = await request.readBodyAsUrlEncodedFormAsync();
Read the request body and parse it as if it were a URL-encoded form. This method will throw an exception if it’s called more than once per request.
The method returns an object whose keys are form field names and whose values are arrays of form field values. For example, given the form data a=1&b=2&b=3
, the form
object would look like this:
{
a: [ "1" ],
b: [ "2", "3" ],
}
- returns form
(object)
- the form names and values
const INPUT_FIELD_NAME = "text";
This constant is available in the production code. It’s the name of the form field that has the user’s input.
TypeScript Types
request.readBodyAsUrlEncodedFormAsync(): Promise<FormData>
The parsed request body.
type FormData = Record<string, string[]>;
JavaScript Primers
No new concepts.
Hints
1
Your test needs to configure the request body.
Change the call to HttpServerRequest.createNull()
.
it("POST asks ROT-13 service to transform text", async () => {
// Arrange
const rot13Client = Rot13Client.createNull();
const rot13Requests = rot13Client.trackRequests();
const clock = Clock.createNull();
const controller = new HomePageController(rot13Client, clock);
const request = HttpServerRequest.createNull({
body: "text=hello%20world",
});
const config = WwwConfig.createTestInstance({
rot13ServicePort: 999,
correlationId: "my-correlation-id",
});
// Act
await controller.postAsync(request, config);
// Assert
assert.deepEqual(rot13Requests.data, [{
port: 999,
text: "some text",
correlationId: "my-correlation-id",
}]);
});
2
Remember to update your assertion.
it("POST asks ROT-13 service to transform text", async () => {
// Arrange
const rot13Client = Rot13Client.createNull();
const rot13Requests = rot13Client.trackRequests();
const clock = Clock.createNull();
const controller = new HomePageController(rot13Client, clock);
const request = HttpServerRequest.createNull({
body: "text=hello%20world",
});
const config = WwwConfig.createTestInstance({
rot13ServicePort: 999,
correlationId: "my-correlation-id",
});
// Act
await controller.postAsync(request, config);
// Assert
assert.deepEqual(rot13Requests.data, [{
port: 999,
text: "hello world",
correlationId: "my-correlation-id",
}]);
});
3
You’re ready to run the test.
It should fail, saying that it expected "hello world"
but got "some text"
.
This means your production code isn’t reading the request body.
4
Make your production code parse the request body.
You can use request.readBodyAsUrlEncodedFormAsync()
. Don’t forget to await
it.
async postAsync(request, config) {
const form = await request.readBodyAsUrlEncodedFormAsync();
const userInput = form[INPUT_FIELD_NAME][0];
await this._rot13Client.transformAsync(
config.rot13ServicePort,
userInput,
config.correlationId,
);
}
TypeScript needs some temporary workarounds to satisfy the compiler:
// TypeScript
async postAsync(request: HttpServerRequest, config: WwwConfig): Promise<HttpServerResponse> {
const form = await request.readBodyAsUrlEncodedFormAsync();
const userInput = form[INPUT_FIELD_NAME]?.[0] ?? "not implemented";
await this._rot13Client.transformAsync(
config.rot13ServicePort,
userInput,
config.correlationId,
);
// DELETE ME: placeholder to satisfy compiler
return await HttpServerResponse.createPlainTextResponse({ status: 501, body: "not implemented" });
}
Complete Solution
Test code:
it("POST asks ROT-13 service to transform text", async () => {
// Arrange
const rot13Client = Rot13Client.createNull();
const rot13Requests = rot13Client.trackRequests();
const clock = Clock.createNull();
const controller = new HomePageController(rot13Client, clock);
const request = HttpServerRequest.createNull({
body: "text=hello%20world",
});
const config = WwwConfig.createTestInstance({
rot13ServicePort: 999,
correlationId: "my-correlation-id",
});
// Act
await controller.postAsync(request, config);
// Assert
assert.deepEqual(rot13Requests.data, [{
port: 999,
text: "hello world",
correlationId: "my-correlation-id",
}]);
});
Production code (JavaScript):
async postAsync(request, config) {
const form = await request.readBodyAsUrlEncodedFormAsync();
const userInput = form[INPUT_FIELD_NAME][0];
await this._rot13Client.transformAsync(
config.rot13ServicePort,
userInput,
config.correlationId,
);
}
Production code (TypeScript):
async postAsync(request: HttpServerRequest, config: WwwConfig): Promise<HttpServerResponse> {
const form = await request.readBodyAsUrlEncodedFormAsync();
const userInput = form[INPUT_FIELD_NAME]?.[0] ?? "not implemented";
await this._rot13Client.transformAsync(
config.rot13ServicePort,
userInput,
config.correlationId,
);
// DELETE ME: placeholder to satisfy compiler
return await HttpServerResponse.createPlainTextResponse({ status: 501, body: "not implemented" });
}