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:

  1. Change the test to configure the request body to be "text=hello%20world". This URL-encoded form data matches what real browsers will send.
  2. Change the test’s assertion to check that "hello world" is sent to the ROT-13 service.

2. Revise HomePageController.postAsync():

  1. Parse the text field from the request body and send it to the ROT-13 service.
  2. 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" });
}

Next challenge

Return to module overview