URL Checker 만들기
url 목록을 전달하고 접속이 가능한지 확인해주는 프로그램을 만들어보자.
(호옥시나 오해할까봐 그러는데 이건 니꼴라스의 강의에 기초해서 만든 것이다)
온갖 url들을 집어넣고 접속을 시켜서 제대로 접속되는지 확인하는 프로그램을 만들것이고, 그리고 중요한 것이✨goroutine✨이나 💥channel💥 기능도 활용해 볼 생각이다.
이번엔 그냥 main.go
에서 다 작성할 예정이다(따로 package 만들어서 도전해봤지만 runtime 에러가 자꾸 나서 포기😧)
STEP1. 그냥 URL Checker 만들기
우선 생각해볼 것은,
{url이름 : status
}의 형태로 받을 것먼저 hitURL
이라는 function을 먼저 작성해준다.
var errCant = errors.New("Cant Connect")
func hitURL(url string) error {
fmt.Println("Checking :", url)
resp, err := http.Get(url)
if err != nil || resp.StatusCode >= 400 {
return errCant
}
return nil
}
우선 http에 들어가는 메서드는 http.Get
이다. 결과값은 response
와 err
이기 때문에 둘 다 확인해서 err가 있거나 statuscode가 400 이상(접속 자체에 문제)인 경우에는 에러 메세지를 띄우도록 한다.
그 다음, main.go
에 들어가 main
function에 아래와 같이 코드를 작성한다.
package main
import (
"errors"
"fmt"
"net/http"
)
func main() {
var results = make(map[string]string)
urls := []string{
"https://www.naver.com/",
"https://www.youtube.com/",
"https://www.github.com/",
"https://your-naduri.herokuapp.com/",
"https://www.podo.com/",
}
for _, url := range urls {
result := "Connected"
err := hitURL(url)
if err != nil {
result = "Failed"
}
results[url] = result
}
for url, result := range results {
fmt.Println(url, result)
}
}
우선 {url이름 : status
}의 형태의 결과를 받을 빈 map을 하나 만들어주는데 초기화를 해주지 않으면 값을 넣을 수 없으니 항상 주의❗하자. 여기서는 make()
를 사용하였다.
그 다음으로, urls라는 url 리스트를 돌면서 확인을 해줄 첫 번째 for문을 작성한다.
위의 코드에서는 아래의 부분에 해당한다.
for _, url := range urls {
result := "Connected"
err := hitURL(url)
if err != nil {
result = "Failed"
}
results[url] = result
여기서는 result의 기본값을 "Connected"
로 하였고, 만약 err가 있다면 result를 "Failed"
로 바꿔주도록 하였다.
이 상태로 실행시키면 map이 띄워지지 않고 한 번에 다 나와서 두 번째 for문을 열어서 따로따로 result들을 보기 좋게 한 줄씩 프린트 하였다.
아래는 실행 결과이다.
실제로 없는 "www.podo.com"은 Failed
로 나온 것을 알 수 있다 ✅
다만, 이렇게 하면 굉장히 느리다.
왜냐, 기본적으로 순차적으로 url들을 돌기 때문에
내가 만들고 heroku로 배포한 "your-naduri.herokuapp.com"같은 경우에는
접속에 시간이 좀 걸리는데 그러면 정체되기 때문에
파일을 다 실행시키는데 약 5~10초가 걸린다(Go를 쓰는 의미가 없다).
STEP2. 빠른 URL Checker 만들기
그 전에, 간단히 ✨goroutine✨과 💥channel💥에 대해서 설명해보겠다(테디베어....🐻)
Go를 매력적으로 하게 만드는 장본인(Concurrency 만만세😍). 다른 프로그램 언어들이 순차적으로 진행되는 (객체지향도 결국 객체 안에서는 순서대로 진행된다) 일들을 병렬적으로 한 번에 쫘아아아악(순서 무관) 해준다. 게다가 사용법도 완전 간단. main
function 안에서 실행시킬 function 앞에 go
라고만 써주면 끝😎
예제를 보자. 코드 참조
package main
import (
"fmt"
"time"
)
func hello(name string) {
for i := 0; i < 3; i++ {
fmt.Println(name, " - ", i)
}
}
func main() {
go hello("Ricky")
go hello("Tom")
go hello("Alice")
time.Sleep(time.Second * 5)
}
위의 코드를 보면 hello
라는 function 안에는 이름을 받아 0,1,2
이라는 숫자를 붙여 출력하는 구조가 짜여져있다.
그리고 아래 main
function 안에는 각각 hello
라는 함수를 호출에 다른 이름들을 넣어주었다. 출력 결과는 아래와 같다.
첫 번째 실행한 결과와 두 번째 실행한 결과가 다른 것을 볼 수 있는데, 그게 바로 GOT✨동시성(Concurrency)✨GOT이다. 한 번에 병렬적으로 thread를 처리한다.
다만 주의할 것이, goroutine
은 main
function이 실행될 때만 발동하기 때문에, goroutine
을 사용할 함수 외에 하나 더 함수를 붙여주던가, 아니면 timeSleep(함수를 몇 초 동안 실행시켜둘지)을 써주어야 한다. 즉, 아래와 같이 써야한다.
func main() {
go hello("Ricky")
go hello("Tom")
go hello("Alice")
time.Sleep(time.Second * 5)
}
// 위와 같이 쓰던가, 아님 아래와 같이 써야한다.
func main() {
go hello("Ricky")
go hello("Tom")
hello("Alice")
}
(그 외에도 익명함수
+Add
+Done
+Wait
등 많은 기능이 있으니 구글링 ㄱㄱ)
goroutine
으로 반환받은 결과를 main
function에게 전달하거나 다른 goroutine
에게 전달하기위한 🗜파이프🗜 같은 거라고 생각하면 된다.
사용법은 이것도 엄청 간단하다. function안에서 채널명 (통상 c)<- 결과값
으로 받아서 main
function에서 <-채널명(통상 c)
으로 주면 된다.
예제를 보자.
package main
import (
"fmt"
"time"
)
func hello(str string, c chan string) {
time.Sleep(time.Second * 3)
c <- str + ", hi!"
}
func main() {
c := make(chan string)
people := [3]string{"Ricky", "Tom", "Alice"}
for _, person := range people {
go hello(person, c)
}
for i := 0; i < len(people); i++ {
fmt.Println(<-c)
}
}
hello
function 안에서 str + ", hi!"
를 결과값으로 c
에 저장해주고(대신 이때 function 정의할 때 채널 받을 거라고 명시해줘야 한다)
그 다음, main
function에 가서 for문 두 개 돌리면서 결과값을 출력시키면 된다. 여기서 특이한 점이 time.Sleep
이 없다는 점이다. 왜냐, 어차피 c
를 people
의 길이대로 다 받아야하기 때문에 그 때 goroutine이 그 때 끝나서 그렇다.
결과를 보자.
똑같이 실행한 두 개의 결과이다. 앞선 코드와 마찬가지로 출력 결과의 순서는 의미없다. 그냥 병렬적으로 처리되서 거의 동시에 실행된 것이다.
자, 다시 본 URL Checker로 돌아가자. 아래와 같이 작성하자.
package main
import (
"fmt"
"net/http"
)
type result struct {
url string
status string
}
func main() {
c := make(chan result)
urls := []string{
"https://www.naver.com/",
"https://www.youtube.com/",
"https://www.github.com/",
"https://your-naduri.herokuapp.com/",
"https://www.podo.com/",
}
for _, url := range urls {
go hitURL(url, c)
}
for i := 0; i < len(urls); i++ {
fmt.Println(<-c)
}
}
func hitURL(url string, c chan result) {
fmt.Println("Checking :", url)
resp, err := http.Get(url)
status := "Connected"
if err != nil || resp.StatusCode >= 400 {
status = "Failed"
}
c <- result{url: url, status: status}
}
우선, result를 struct로 담아주게 빈 struct를 만들어주고, main
function에 채널을 추가해준다.
그 다음 hitURL
function부터 보면, struct에 들어갈 결과값들(여기서는 result{url: url, status: status}
)를 c
채널에 넣어준다.
그러고 main
function에 가서 go hitURL(url, c)
로 받아주고 그 아래 for문으로 fmt.Println(<-c)
로 싸악 다 출력해준다.
이렇게 하면 아래와 같은 결과가 출력된다.
goroutine
을 적용시켰기때문에 순서는 상관없고, 중요한 건 이 모든 것이 1초도 안 되서 실행되었다는 것이다.
goroutine
을 사용하지 않았을 때는 5~10초정도 걸렸는데 이걸 1초로 줄였다 ✅
마무리🙆
Go의 진짜 매력을 알아볼 수 있는 재밌는 실습이었던 것 같다. 이 기능을 배우니 Go에 대한 흥미가 더 많아졌다🤲💯
딥러닝 프로젝트를 얼른 대충 마무리 짓고 더 공부하고 싶다.