각 대상에 결과 하나씩 반환하기

GolangBeginner
지금 연습하기

소개

Garden Night 행사에는 접수, 지도, 직원 페이지, 티켓 정보 등 여러 로컬 웹 진입점이 있습니다. 개장 전에 담당자는 할당한 모든 점검에 대해 정확한 결과를 받아야 합니다. 접근이 거부되거나 응답이 느린 진입점도 보고서에서 사라져서는 안 됩니다.

Go 점검 함수를 완성하고 실제 JSON 보고서를 저장합니다. Go 함수, 구조체, 슬라이스, 모듈, 기본 채널 문법을 알고 있으며, Building a Lost and Found API를 이수했거나 그에 준하는 HTTP 경험이 있다고 가정합니다. HTTP 클라이언트 오류, 유한한 제한 시간, 응답 자원에 대한 책임은 사용 전에 설명합니다. 대상은 모두 통제된 로컬 테스트 서비스입니다. 이 도구는 행사 전 한 번 수행하는 점검이며 인터넷 스캐너나 모니터링 서비스가 아닙니다. 이 단원은 독립적으로 시작하므로 이전 VM의 파일이 필요하지 않습니다. 읽기와 일반적인 오류 처리를 포함해 약 55분을 예상하세요. 이는 저자의 추정치입니다.

작은 성공 사례의 결과 읽기

이 단계에서는 동작하는 작은 사례를 실행하고 결과 한 건이 무엇을 식별하는지 알아봅니다. Workspace에서 시작하세요. Go 소스와 행사 로컬 서비스가 준비되어 있습니다. 아래 명령은 모두 Workspace에서 실행합니다.

cd는 디렉터리를 선택합니다. Go는 /usr/local/go/bin에 있고, export는 이 디렉터리를 현재 셸의 기존 $PATH 앞에 추가합니다. 현재 터미널 세션에만 적용됩니다. 새 셸을 열면 두 줄을 다시 실행하세요. 전역 셸 설정 파일은 바꿀 필요가 없습니다.

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

cat은 파일을 편집하지 않고 읽습니다. 정상 작업 두 건과 Go 데이터 구조를 확인하세요.

cat normal.json types.go

각 대상에는 해당 작업을 식별하는 id, 요청할 url, 응답 본문에서 기대하는 문자열 want가 있습니다. 결과는 id와 url을 유지합니다. kind는 업무 결과이고, status는 수신한 HTTP 상태 코드입니다. 응답 헤더를 받지 못했다면 0입니다. 작업과 URL은 별개입니다. 두 사람이 같은 진입점에 대해 독립적인 점검을 요청할 수 있습니다.

go run .은 현재 디렉터리의 Go 패키지를 빌드하고 실행합니다. 준비된 명령은 -input으로 JSON 목록을, -workers로 동시 점검 수의 상한을, -timeout으로 대상별 HTTP 시간 예산을 받습니다. 200ms는 200밀리초입니다. 여기서는 워커 하나가 빠른 진입점 두 곳을 차례대로 점검합니다. 셸의 >는 표준 출력을 파일로 보내며, 파일이 이미 있으면 교체합니다. 오류는 계속 터미널에 표시됩니다.

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

실제로 저장한 결과를 읽으세요.

cat normal-report.json

두 작업 모두 원래 ID와 URL을 유지하면서 kind: "ok", status: 200을 보여야 합니다. 200은 서버가 이 GET을 받아들였다는 뜻이지, 기대한 페이지 내용이 있다는 증거는 아닙니다. 초기 구현은 쉬운 경우만 처리합니다. 다음에는 다른 진입점에서도 의미 있는 결과를 만들겠습니다.

go: command not found가 나타나면 현재 세션의 export를 다시 실행하세요. 파일을 찾지 못하면 cd를 다시 실행하고 파일 이름을 확인합니다. 나중에 Go가 컴파일 오류를 보고하면 파일 이름과 줄 번호로 해당 작은 영역을 찾아 고치세요. 다음 단계에서는 파일 전체를 교체하지 않고 변경 사항을 편집하고 저장하고 확인하는 방법을 배웁니다. main.go, types.go, 모든 입력 JSON 파일과 준비된 로컬 서비스는 변경하지 마세요.

응답과 요청 실패 구분하기

이 단계에서는 Workspace에서 작은 Check 함수를 판단 하나씩 편집합니다. 대상 하나의 의미에 집중할 수 있도록 run.go는 순차 처리로 유지하세요. 먼저 초기 구현을 읽습니다.

cat check.go

결과는 이 작업의 ID와 URL로 시작합니다. client.Get은 응답과 오류를 반환합니다. 기존 오류 분기는 연결 실패를 처리합니다. 호출이 성공하면 응답 본문을 닫는 책임이 이 함수로 넘어옵니다. 그 오류 분기 바로 뒤에 defer response.Body.Close()를 유지하세요. defer는 조기 반환을 포함해 함수가 반환될 때 실행됩니다. 수신 상태 할당도 유지합니다. 제공된 완성 프로그램으로 교체하지 않고 이 초기 구현을 확장합니다.

국소적으로 편집하고 복구하기

편집 전에 소스를 백업하세요. cp는 파일을 복사합니다. 다른 .go 파일은 이 패키지의 일부로 컴파일되어 Check를 두 번 정의할 수 있으므로 .bak 확장자가 중요합니다.

cp check.go check.go.bak

터미널 편집기로 기존 파일을 엽니다.

nano check.go

nano에서는 화살표 키로 이동하고 입력하여 텍스트를 넣습니다. Backspace와 Delete는 문자를 지웁니다. Ctrl+K는 현재 행을 잘라 내므로 그 행 전체를 지워야 할 때만 사용하세요. Ctrl은 다른 키를 누르는 동안 Control을 누르라는 뜻입니다. Ctrl+W를 누르고 func Check를 입력한 뒤 Enter로 검색하며 Ctrl+C로 검색을 취소합니다. 커서 위치에서 편집하세요. 저장하려면 Ctrl+O를 누르고 파일명이 여전히 check.go인지 확인한 뒤 Enter를 누릅니다. Ctrl+X로 종료합니다. 다시 편집할 때마다 nano check.go를 실행하세요. 잘못된 파일을 열었거나 원하지 않는 변경을 했다면 Ctrl+X를 누르고 저장 질문에 N으로 답합니다. 원하지 않는 파일명 입력창에서는 Ctrl+C로 취소합니다. pwd로 디렉터리를 확인하고 첫 단계의 cd를 사용하여 올바른 파일을 연 뒤 다시 시도하세요.

각 편집 후 gofmt -w check.go는 파일을 제자리에서 정리합니다. 중괄호 누락 같은 문법 오류에는 파일명과 줄 번호가 포함됩니다. 해당 파일을 다시 열고 근처 문장을 검색하여 그 부분을 고치세요. 저장 후 cat check.go로 실제 디스크 내용을 확인합니다. 이미 작성한 작동하는 부분은 유지하세요. 일반적인 소스 오류 때문에 VM을 초기화하거나 함수 전체를 덮어쓸 필요는 없습니다.

서버가 이 진입점 요청을 받아들였는지 판단하기

403 응답은 HTTP 교환이 성공한 것이므로 Get이 자동으로 Go 오류를 반환하는 것은 아닙니다. 이 행사는 정확히 상태 200을 기대하며 다른 수신 상태는 http_error입니다. 이 행사의 업무 규칙이지 모든 다른 2xx 상태가 보편적으로 HTTP 실패라는 뜻은 아닙니다.

Check에서 response.StatusCode를 저장한 뒤 200을 뜻하는 표준 라이브러리 상수 http.StatusOK와 비교하는 조건을 작성하세요. 다른 상태라면 결과의 kind를 "http_error"로 설정하고 그 결과를 즉시 반환합니다. 지금은 기존 마지막 "ok" 결과를 유지하세요. 여기서 반환하면 기존 defer가 본문을 닫으므로 불필요한 오류 본문을 읽을 때까지 기다리지 않습니다.

저장하고 nano를 종료한 뒤 정리합니다.

gofmt -w check.go

실행 전에 정상 작업과 직원 작업이 무엇을 보고해야 할지 예상하세요. 모든 작업을 편집하지 않고 확인할 수 있습니다.

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

이 단계에서 직원 작업은 403인 http_error여야 하지만 수신한 200 페이지는 내용이 틀려도 아직 ok입니다. 느린 티켓 점검도 아직 connection_error입니다. 아래에서 시간 초과를 구분합니다. 의도적으로 남아 있는 이 오류들이 다음 편집에서 바꿔야 할 것을 보여 줍니다. 두 접수 ID를 포함해 모든 입력 ID에 결과가 계속 있어야 합니다.

수신 페이지 내용 확인하기

200 응답의 io.ReadAll(response.Body)는 본문 바이트와 오류를 반환합니다. import 그룹에 net/http와 함께 io, strings를 추가하세요. import ( 다음에 행마다 따옴표로 감싼 패키지 이름을 쓰고 )로 닫습니다. import는 파일이 사용하는 패키지의 이름입니다. 사용하지 않는 import는 컴파일 오류이므로 호출을 구현할 때 추가하세요.

정상 수신 본문을 분류하기 전에 body와 err로 읽습니다. 읽기가 실패하면 우선 "connection_error" 결과를 반환하세요. 이미 수신한 상태는 유지합니다. 읽기에 성공하면 먼저 result.Kind = "ok"를 설정하거나 유지합니다. 그 할당 뒤이면서 마지막 반환 전에 기대한 표식을 검사하세요. string(body)는 바이트를 텍스트로 바꾸고 strings.Contains(string(body), target.Want)는 표식 포함 여부를 확인합니다. false이면 kind를 "content_mismatch"로 바꿉니다. 이 조건 뒤에 다시 "ok"를 할당하면 불일치를 덮어쓰므로 그러지 마세요. Go의 !는 불리언을 부정합니다. 성공적으로 읽은 본문에는 마지막 반환 하나를 유지하세요.

저장하고 종료한 뒤 정리하여 같은 실제 작업을 반복합니다.

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

이제 잘못된 지도는 200인 content_mismatch여야 하고 두 접수 작업은 200인 ok, 직원 결과는 403인 http_error를 유지합니다. 상태를 수신했다는 사실은 표식이 일치한다는 증거가 아닙니다. 티켓 시간 초과에는 여전히 별도의 분류가 필요합니다.

유한한 기다림과 다른 실패 구분하기

HTTP 클라이언트의 예산은 응답 본문 읽기를 포함합니다. 헤더 전의 시간 초과는 상태 0을 남기고 200 헤더 후의 시간 초과는 상태 200을 유지해야 합니다. import에 net을 추가하세요. net.Error에는 Timeout() 메서드가 있습니다. 다음 짧은 Go 패턴은 메서드를 호출하기 전에 오류가 해당 인터페이스를 지원하는지 확인합니다.

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

타입 단언은 변환된 값과 불리언을 반환합니다. &&는 왼쪽이 true일 때만 오른쪽을 평가합니다. 요청 오류 분기와 본문 읽기 오류 분기 모두에서 "connection_error"를 기본값으로 유지한 뒤 이 보호된 검사가 성공하면 "timeout"으로 바꾸세요. 각 분기의 반환과 원래 작업 식별자를 유지합니다. 저장하고 종료한 뒤 정리하세요.

gofmt -w check.go

기존 서비스는 유용한 통제 사례 두 가지도 제공합니다. /garden/bodylate/check는 유한한 600ms 본문 지연 전에 200 헤더를 보내고, /garden/deniedbody/check는 같은 불필요한 본문 지연 전에 403 헤더를 보냅니다. 준비된 모든 JSON 목록을 그대로 두고 이 사례를 살펴볼 보충 입력을 만드세요.

nano body-probes.json

이 JSON 배열을 입력합니다. JSON은 큰따옴표로 감싼 키와 문자열, 필드와 객체 사이의 쉼표, 목록 바깥의 대괄호를 사용합니다. 마지막 필드나 객체 뒤에는 쉼표가 없습니다.

[
  {"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"}
]

Ctrl+O로 저장하고 body-probes.json을 확인한 뒤 Enter, Ctrl+X로 종료합니다. 사용 전에 실제 파일을 읽으세요.

cat body-probes.json

두 결과를 예상한 뒤 200ms 예산으로 요청합니다.

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

body-200은 상태 200인 timeout, body-403은 상태 403인 http_error여야 합니다. 거부는 본문을 읽기 전에 알려지므로 본문 지연 때문에 시간 초과로 바뀌면 안 됩니다. 클라이언트 시간 초과는 기다림을 멈추지만 서버는 유한한 작업을 나중에 끝낼 수 있습니다. 총 경과 시간으로 정확성을 추론하지 마세요.

마지막으로 원래 전체 작업 목록을 다시 실행합니다.

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

행 위치가 아니라 ID로 대조하세요. 예상 결과는 ok 두 건, 403인 http_error, 200인 content_mismatch, 0인 timeout, 0인 connection_error입니다. 업무상 실패는 일반 보고서 값이므로 명령은 여전히 성공 종료합니다. 컴파일이나 JSON 문법 오류는 명령이 작업을 수행하지 못했다는 뜻입니다. 파일명과 위치로 자신의 편집을 고친 뒤 그 명령을 반복하세요. 코스 검증은 독립 입력에서 원래 소스와 본문 닫기 동작을 시험하며 특정 편집기나 보충 파일명을 요구하지 않습니다.

전체 보고서 저장하고 읽기

이 단계에서는 모든 작업을 유지하면서 유용한 보고서를 저장합니다. 준비된 순차 반복문을 읽으세요.

cat run.go

http.Client{Timeout: budget}는 각 요청에 유한한 시간 예산을 설정합니다. 반복문은 대상마다 Check를 한 번 호출하고, 오류를 포함한 모든 결과를 추가합니다. 첫 실패에서 반환하거나 URL을 키로 사용하는 map에 결과를 모아서는 안 됩니다. 초기화한 빈 슬라이스는 작업이 없을 때 빈 JSON 배열도 생성합니다.

이제 전체 보고서를 저장하세요. 의도적으로 교체하는 것은 보고서 파일뿐이며 입력 작업은 그대로 둡니다.

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

실제로 생성한 파일을 읽으세요.

cat report.json

ID를 이용해 두 접수 작업이 같은 URL을 가리키는지 확인합니다. 각각 정확히 한 번 나와야 합니다. 403 결과는 서버가 응답하고 요청을 거부했다는 뜻이지 연결 장애가 아닙니다. 지도 표식이 다르면 200이어도 내용을 조사해야 합니다. 시간 초과는 이번 점검의 예산을 넘었다는 의미이며, 서비스가 영원히 응답하지 않는다는 뜻은 아닙니다.

마지막으로 준비된 빈 작업 목록을 실행합니다. 표준 출력이 직접 표시되므로 유용한 보고서를 교체하지 않습니다.

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

[]가 나타나고 정상 종료해야 합니다. report.json, 수정한 check.go, 모든 원본 입력을 유지하세요. 코스 검증은 이 보고서를 읽을 뿐 아니라 독립된 로컬 테스트 서비스에서 원래 소스를 실행합니다. 복사한 행이나 정적인 결과는 실제 요청을 대신할 수 없습니다.

요약

각 로컬 진입점 작업에 정확한 결과를 만들고, HTTP 거부와 전송 실패를 구분하며, 내용 불일치를 식별했습니다. 또한 HTTP에 유한한 시간 예산을 적용하고 수신한 모든 응답 본문을 닫았습니다. 완전한 JSON 보고서를 저장하고 읽으면서 같은 URL도 서로 다른 작업 ID로 유지했습니다. 다음 단원에서는 이 판단을 유지한 채 소수의 워커가 작업을 분담하는 방식을 바꿉니다.