Return One Result for Every Target

GolangBeginner
Practice Now

Introduction

The Garden Night event has several local web entrances: check-in, a map, a staff page, and ticket information. Before opening, organizers need an honest result for every assigned check. A refused or slow entrance must not disappear from the report.

You will complete a Go checking function and save a real JSON report. You should know Go functions, structs, slices, modules, and basic channel syntax, plus Building a Lost and Found API or equivalent HTTP experience. HTTP client errors, finite timeouts, and response ownership are explained before use. All targets are controlled local fixtures; this is a one-time event check, not an Internet scanner or monitoring service. This independent unit needs no earlier VM files. Allow about 55 minutes including reading and ordinary mistakes; this is an author estimate.

Read a Small Successful Check

In this step, run the small working case and learn what one result identifies. Start in Workspace. The prepared Go source and local event service are already available. All commands below run in Workspace.

cd selects the directory. Go lives in /usr/local/go/bin; export adds it before this shell's existing $PATH. This lasts only for this terminal session. Repeat these two lines after opening a new shell; no global shell files need changing.

cd /home/labex/project/site-checker
export PATH=/usr/local/go/bin:$PATH

cat reads files without editing them. Read the two normal assignments and the Go data shapes:

cat normal.json types.go

Each target has an id identifying this assignment, a url to request, and want, the text expected in its response body. The result retains id and url; kind is the business outcome and status is the received HTTP status, or zero when no response headers arrived. An assignment is distinct from its URL: two people can request separate checks of the same entrance.

go run . builds and runs the Go package in this directory. The prepared command accepts -input for a JSON list, -workers for the maximum number of simultaneous checks, and -timeout for each target's HTTP time budget. 200ms means 200 milliseconds. Here, one worker checks the two fast entrances in sequence. The shell's > sends standard output into a file, replacing that file if it already exists; errors still appear in the terminal.

go run . -input normal.json -workers 1 -timeout 200ms > normal-report.json

Read the real saved result:

cat normal-report.json

Both assignments should have kind: "ok" and status: 200, with their original IDs and URLs. 200 means the server accepted this GET; it alone cannot show that the expected page content is present. The first draft only understands the easy path. Next you will make the result meaningful for the other entrances.

If you see go: command not found, repeat the session's export. If a file cannot be found, repeat the cd and check its spelling. If Go reports a compilation error later, use its filename and line number to locate and repair that small area. The next step teaches how to edit, save, and check your changes without replacing your whole file. Keep main.go, types.go, all input JSON files, and the prepared local service unchanged.

Distinguish Replies from Failed Requests

In this step, edit the small Check function in Workspace, one decision at a time. Keep run.go serial so you can concentrate on one target's meaning. First read the draft:

cat check.go

The result starts with this assignment's ID and URL. client.Get returns a response and an error. The existing error branch handles a failed connection; a successful call gives this function responsibility for closing the response body. Keep defer response.Body.Close() immediately after that error branch: defer runs when the function returns, including an early return. Keep the received status assignment too. You will extend this draft, rather than replace it with a supplied finished program.

Edit and recover locally

Before editing, make a source backup. cp copies a file; the .bak ending matters because another .go file would be compiled as part of this package and could define Check twice.

cp check.go check.go.bak

Open the existing file in the terminal editor:

nano check.go

In nano, move with the arrow keys. Type to insert text; Backspace and Delete remove characters. Ctrl+K cuts the current line, so use it only when that whole line should be removed. Ctrl means hold Control while pressing the other key. Use Ctrl+W, type func Check, then Enter to search; Ctrl+C cancels a search. Edit at the cursor. To save, press Ctrl+O, check that the filename is still check.go, and press Enter. Press Ctrl+X to exit. Repeat nano check.go whenever you need another edit. If you opened the wrong filename or made unintended changes, press Ctrl+X, then N when asked whether to save. At an unwanted filename prompt, Ctrl+C cancels the prompt. Run pwd to read your directory, use the first step's cd, and open the correct file before trying again.

After each edit, gofmt -w check.go formats the file in place. A missing brace or another syntax error includes a filename and line number: reopen that file, search for the nearby statement, and repair that area. Use cat check.go after saving to inspect what is actually on disk. Keep the working parts you have already written; an ordinary source mistake does not require resetting the VM or overwriting the entire function.

Decide whether the server accepted this entrance

A 403 response is a successful HTTP exchange, so Get does not automatically return a Go error for it. This event expects exactly status 200; other received statuses mean http_error. This is the event's business rule, not a claim that every other 2xx status is universally an HTTP failure.

In Check, after saving response.StatusCode, write a condition comparing it with http.StatusOK, the standard library's constant for 200. For a different status, set the result's kind to "http_error" and return that result immediately. Keep the existing final "ok" result for now. Returning here closes the body through the existing defer without waiting to read an unneeded error body.

Save, exit nano, and format:

gofmt -w check.go

Before running, predict what the normal and staff assignments should report. You can inspect all assignments without editing them:

cat targets.json
go run . -input targets.json -workers 1 -timeout 200ms

At this stage, the staff assignment should be http_error with 403, while received 200 pages are still ok even when their contents are wrong. The slow ticket check is still called connection_error; you will distinguish timeouts below. These deliberate remaining errors show what your next edits must change. Each input ID, including both check-in IDs, must still have a result.

Check the received page content

For a 200 response, io.ReadAll(response.Body) returns the body bytes and an error. Add io and strings alongside net/http in an import group: import ( followed by one quoted package name per line, then ). Imports name packages your file uses; unused imports are compiler errors, so add them when implementing their calls.

Before classifying a successfully received body, read it into body and err. If reading fails, return a result with "connection_error" for now. Keep the already received status in the result. After a successful read, first set or keep result.Kind = "ok". Then, after that assignment and before the final return, test the expected marker. string(body) converts the bytes to text, and strings.Contains(string(body), target.Want) answers whether that text includes the marker. When that answer is false, change the kind to "content_mismatch". Do not assign "ok" again after this condition, or it would overwrite the mismatch. In Go, ! negates a boolean. Keep one final return for the successfully read body.

Save and exit, then format and repeat the same real assignments:

gofmt -w check.go
go run . -input targets.json -workers 1 -timeout 200ms

Now the wrong map should be content_mismatch with 200, while both check-in assignments remain ok with 200 and the staff result remains http_error with 403. A received status is not proof that the marker matched. The ticket timeout still needs its own category.

Distinguish a finite wait from another failure

The HTTP client's budget includes reading the response body. A timeout before headers leaves status zero; a timeout after 200 headers must retain status 200. Add net to the imports. A net.Error has a Timeout() method. This short Go pattern checks whether an error supports that interface before calling its method:

if networkError, ok := err.(net.Error); ok && networkError.Timeout() {
    // The operation exceeded its time budget.
}

The type assertion returns a converted value and a boolean. && evaluates its right side only if the left side is true. In both your request-error branch and your body-read-error branch, keep "connection_error" as the default, then change it to "timeout" when this guarded test succeeds. Keep each branch's return and the original task identity. Save, exit, and format:

gofmt -w check.go

The existing service also offers two useful controlled cases: /garden/bodylate/check sends 200 headers before its finite 600ms body delay; /garden/deniedbody/check sends 403 headers before that same unneeded body delay. Create a supplementary input to examine these cases, leaving all prepared JSON lists intact:

nano body-probes.json

Enter this JSON array. JSON uses double-quoted keys and strings, commas between fields and objects, and square brackets around the list. There is no comma after the last field or object.

[
  {"id": "body-200", "url": "http://127.0.0.1:8080/garden/bodylate/check", "want": "GARDEN OPEN"},
  {"id": "body-403", "url": "http://127.0.0.1:8080/garden/deniedbody/check", "want": "GARDEN OPEN"}
]

Save with Ctrl+O, confirm body-probes.json and Enter, then exit with Ctrl+X. Read the actual file before using it:

cat body-probes.json

Predict the two outcomes, then request them with a 200ms budget:

go run . -input body-probes.json -workers 1 -timeout 200ms

Expect body-200 to be timeout with status 200 and body-403 to be http_error with status 403. The refusal is known before reading its body, so its body delay must not turn it into a timeout. Client timeout stops waiting; the server can finish its finite work later. Do not infer correctness from total elapsed time.

Finally, rerun the original full assignment list:

go run . -input targets.json -workers 1 -timeout 200ms

Match by ID, not row position. Expect two ok results, one http_error with 403, one content_mismatch with 200, one timeout with zero, and one connection_error with zero. Business failures are ordinary report values, so the command still finishes successfully. A compiler or JSON syntax error means the command could not do its job: use its filename and location to repair your local edit, then repeat that command. The course's check exercises your original source and body-close behavior on independent inputs; it does not require a particular editor or the supplementary filename.

Keep and Read the Complete Report

In this step, save a useful report while retaining every assignment. Read the prepared sequential loop:

cat run.go

http.Client{Timeout: budget} puts the finite budget on each request. The loop calls Check once per target and appends every result, including errors. It must not return early on the first failed entrance or collect results in a map keyed by URL. The initialized empty slice also produces an empty JSON array for zero assignments.

Now save your full report. This deliberately replaces only your report file; the input assignments remain intact.

go run . -input targets.json -workers 1 -timeout 200ms > report.json

Read the actual file you have produced:

cat report.json

Use the IDs to trace the two check-in tasks to the same URL. Each must appear exactly once. The 403 result tells organizers that a server replied and refused the request; it is not a connection outage. The wrong map marker tells them to investigate content despite status 200. A timeout says this check exceeded its budget, not that the service will never respond.

Finally, exercise the prepared empty assignment list. Standard output is shown directly, so this does not replace your useful report:

go run . -input empty.json -workers 1 -timeout 200ms

Expect [] and normal completion. Keep report.json, your updated check.go, and all original inputs. The course checks run your unchanged source against separate local fixtures as well as reading this report, so copied or static report rows do not substitute for real requests.

Summary

You created one truthful result for each assigned local entrance, distinguished HTTP refusals from transport failures, recognized content mismatches, applied finite HTTP budgets, and closed every received body. You saved and read a complete JSON report, preserving duplicate URLs as separate task IDs. The next unit keeps these decisions and changes how a small number of workers share the work.