Subject: Re: [PATCH v5 05/10] test-http-server: add HTTP error response function

From: Victoria Dye <[email-masked]>
Date: Thu, 12 Jan 2023 12:35:19 -0800

Matthew John Cheetham via GitGitGadget wrote:
> From: Matthew John Cheetham <[email-masked]>
> 
> Introduce a function to the test-http-server test helper to write more
> full and valid HTTP error responses, including all the standard response
> headers like `Server` and `Date`.

It took me a second to figure out, but this patch combines the content of
patches 4, 5, and 6 from the last iteration. After squashing those three
patches from v4 together locally, the range-diff is actually pretty simple
(see below). 

Out of curiosity, why did you combine those patches? I don't feel strongly
about changing it, but the smaller, incremental patches in the previous
version were a bit easier to review.

In any case, this version addresses my feedback from [1], [2], and [3] - the
explanatory comments are particularly helpful. Thanks!

[1] https://lore.kernel.org/git/[email-masked]/
[2] https://lore.kernel.org/git/[email-masked]/
[3] https://lore.kernel.org/git/[email-masked]/

Range diff v4 (patches 4-6, squashed) vs. v5 (this patch)

4:  127827637e !  5:  6f66bf146b test-http-server: add HTTP error response function
    @@ Commit message
     
      ## t/helper/test-http-server.c ##
     @@ t/helper/test-http-server.c: enum worker_result {
    - 	WR_STOP_THE_MUSIC = (WR_IO_ERROR | WR_HANGUP),
    + 	WR_HANGUP   = 1<<1,
      };
      
     +/*
    @@ t/helper/test-http-server.c: enum worker_result {
     +		hp = strbuf_detach(&h, NULL);
     +		string_list_append(&req->header_list, hp);
     +
    -+		/* store common request headers separately */
    ++		/* also store common request headers as struct req members */
     +		if (skip_prefix(hp, "Content-Type: ", &hv)) {
     +			req->content_type = hv;
     +		} else if (skip_prefix(hp, "Content-Length: ", &hv)) {
    @@ t/helper/test-http-server.c: enum worker_result {
     +
     +	if (!initialized) {
     +		smart_http_regex = xmalloc(sizeof(*smart_http_regex));
    ++		/*
    ++		 * This regular expression matches all dumb and smart HTTP
    ++		 * requests that are currently in use, and defined in
    ++		 * Documentation/gitprotocol-http.txt.
    ++		 *
    ++		 */
     +		if (regcomp(smart_http_regex, "^/(HEAD|info/refs|"
     +			    "objects/info/[^/]+|git-(upload|receive)-pack)$",
     +			    REG_EXTENDED)) {
    @@ t/helper/test-http-server.c: enum worker_result {
     +		!regexec(smart_http_regex, req->uri_path.buf, 0, NULL, 0);
     +}
     +
    -+static enum worker_result do__git(struct req *req, const char *user)
    ++static enum worker_result do__git(struct req *req)
     +{
     +	const char *ok = "HTTP/1.1 200 OK\r\n";
     +	struct child_process cp = CHILD_PROCESS_INIT;
     +	int res;
     +
    -+	if (write(1, ok, strlen(ok)) < 0)
    ++	/*
    ++	 * Note that we always respond with a 200 OK response even if the
    ++	 * http-backend process exits with an error. This helper is intended
    ++	 * only to be used to exercise the HTTP auth handling in the Git client,
    ++	 * and specifically around authentication (not handled by http-backend).
    ++	 *
    ++	 * If we wanted to respond with a more 'valid' HTTP response status then
    ++	 * we'd need to buffer the output of http-backend, wait for and grok the
    ++	 * exit status of the process, then write the HTTP status line followed
    ++	 * by the http-backend output. This is outside of the scope of this test
    ++	 * helper's use at time of writing.
    ++	 *
    ++	 * The important auth responses (401) we are handling prior to getting
    ++	 * to this point.
    ++	 */
    ++	if (write(STDOUT_FILENO, ok, strlen(ok)) < 0)
     +		return error(_("could not send '%s'"), ok);
     +
    -+	if (user)
    -+		strvec_pushf(&cp.env, "REMOTE_USER=%s", user);
    -+
     +	strvec_pushf(&cp.env, "REQUEST_METHOD=%s", req->method);
     +	strvec_pushf(&cp.env, "PATH_TRANSLATED=%s",
     +			req->uri_path.buf);
    @@ t/helper/test-http-server.c: enum worker_result {
     +	cp.git_cmd = 1;
     +	strvec_push(&cp.args, "http-backend");
     +	res = run_command(&cp);
    -+	close(1);
    -+	close(0);
    ++	close(STDOUT_FILENO);
    ++	close(STDIN_FILENO);
     +	return !!res;
     +}
     +
     +static enum worker_result dispatch(struct req *req)
     +{
     +	if (is_git_request(req))
    -+		return do__git(req, NULL);
    ++		return do__git(req);
     +
    -+	return send_http_error(1, 501, "Not Implemented", -1, NULL,
    ++	return send_http_error(STDOUT_FILENO, 501, "Not Implemented", -1, NULL,
     +			       WR_OK | WR_HANGUP);
     +}
     +
    @@ t/helper/test-http-server.c: enum worker_result {
      	char *client_port = getenv("REMOTE_PORT");
      	enum worker_result wr = WR_OK;
     @@ t/helper/test-http-server.c: static enum worker_result worker(void)
    - 	set_keep_alive(0);
    + 	set_keep_alive(0, logerror);
      
      	while (1) {
    --		if (write_in_full(1, response, strlen(response)) < 0) {
    +-		if (write_in_full(STDOUT_FILENO, response, strlen(response)) < 0) {
     -			logerror("unable to write response");
     -			wr = WR_IO_ERROR;
     -		}
     +		req__release(&req);
     +
    -+		alarm(init_timeout ? init_timeout : timeout);
    ++		alarm(timeout);
     +		wr = req__read(&req, 0);
     +		alarm(0);
     +
    -+		if (wr & WR_STOP_THE_MUSIC)
    ++		if (wr != WR_OK)
     +			break;
      
     +		wr = dispatch(&req);
    - 		if (wr & WR_STOP_THE_MUSIC)
    + 		if (wr != WR_OK)
      			break;
      	}
     
    @@ t/t5556-http-auth.sh (new)
     +
     +test_description='test http auth header and credential helper interop'
     +
    ++TEST_NO_CREATE_REPO=1
     +. ./test-lib.sh
     +
     +test_set_port GIT_TEST_HTTP_PROTOCOL_PORT
     +
     +# Setup a repository
     +#
    -+REPO_DIR="$(pwd)"/repo
    ++REPO_DIR="$TRASH_DIRECTORY"/repo
     +
     +# Setup some lookback URLs where test-http-server will be listening.
     +# We will spawn it directly inside the repo directory, so we avoid
    @@ t/t5556-http-auth.sh (new)
     +# The server will shutdown if/when we delete it (this is easier than
     +# killing it by PID).
     +#
    -+PID_FILE="$(pwd)"/pid-file.pid
    -+SERVER_LOG="$(pwd)"/OUT.server.log
    ++PID_FILE="$TRASH_DIRECTORY"/pid-file.pid
    ++SERVER_LOG="$TRASH_DIRECTORY"/OUT.server.log
     +
     +PATH="$GIT_BUILD_DIR/t/helper/:$PATH" && export PATH
     +
    @@ t/t5556-http-auth.sh (new)
     +
     +test_expect_success 'http auth anonymous no challenge' '
     +	test_when_finished "per_test_cleanup" &&
    -+	start_http_server --allow-anonymous &&
    ++	start_http_server &&
     +
     +	# Attempt to read from a protected repository
     +	git ls-remote $ORIGIN_URL