본문 바로가기
GO

[Go] 압축, 암호화, 정렬, 컨테이너, 프로토콜, 서버, 에러

by 수픽 2020. 8. 17.

UNIT 52 :: 압축 사용하기


#compress/gzip 패키지

func NewReader(r io.Reader) (*Reader, error) /*io.Reader 인터페이스로 io.Reader 인터페이스를 따르는 압축 해제 인스턴스 생성*/
func NewWriter(w io.Writer) *Writer //io.Writer 인터페이스로 io.Writer 인터페이스를 따르는 압축 인스턴스 생성

#io/ioutil 패키지

func ReadAll(r io.Reader) ([]byte, error) // io.Reader를 끝(EOF)까지 읽어서 바이트 슬라이스로 리턴

read가 들어가는 건 압축해제에, write가 들어가는 건 압축에 사용된다.

 

1. 압축

: gzip 알고리즘을 사용하여 데이터를 압축한 뒤 파일로 저장하는 코드

gzip 알고리즘 압축

- 문자열은 []byte 형식으로 변환

- close 메서드로 압축 인스턴스를 반드시 닫아줘야 함

 

2. 압축 해제

: hello.txt.gz의 압축을 해제하는 코드

압축 해제

gzip 알고리즘 외에도 다른 압축 알고리즘들이 존재. (ex. compress/bzip2..)

 

unit 53 :: 암호화 사용하기


1. 해시 알고리즘

단방향 암호화 알고리즘. 패스워드 저장할 때 사용 ex) MD5, SHA1, SHA256, SHA512.

 

#crypto/sha512 패키지

func New() hash.Hash //SHA512 해시 인스턴스 생성
func Sum512(data []byte) [Size]byte //Sha512 해시 계산하여 리턴
func (d *digest) Write(p []byte)(nn int, err error) //해시 인스턴스에 데이터 추가
func (d0 *digest) Sum(in []byte) []byte //해시 인스턴스에 저장된 데이터의 SHA512 해시 값 추출

sha512 해시 값을 추출하는 방법.

sha512

sha512.sum512 함수에 []byte 형식으로 데이터를 넣어주면 해시 값이 리턴된다. new 함수를 이용하여 새로운 인스턴스를 생성하여 write 함수에 데이터를 넣고 sum 함수로 해시 값을 만들면 된다.

 

2. AES 대칭키 알고리즘

#crypto/aes 패키지

func NewCipher(key []byte)(cipher.Block, error) //대칭키 암호화 블록 생성
func (c *aesCipher) Encrypt(dst, src []byte) //평문을 AES 알고리즘으로 암호화
func (c *aesCipher) Decrypt(dst, src []byte) //AES 알고리즘으로 암호화된 데이터를 평문으로 복호화

 

aes 암호화&복호화

- 블록 암호화 알고리즘이므로 키와 암호화할 데이터의 크기가 일정해야 함

- encrypt 함수에 넣으면 암호화, decrypt 함수에 넣으면 복호화

- 데이터를 잘라서 암호화하면 보안에 취약 -> ECB 방식

- 긴 데이터를 안전하게 암호화 -> CBC 방식

 

2-1. CBC 방식 암호화

#crypto/cipher 패키지

func NewCBCEncrypter(b Block, iv []byte) BlockMode //암호화 블록과 초기화 벡터로 암호화 블록 모드 인스턴스 생성
func (x *cbcEncrypter) CryptBlocks(dst, src[]byte) //암호화 블록 모드 인스턴스로 암호화
func NewCBCDecrypter(b Blcok, iv []byte) BlockMode //암호화 블록과 초기화 벡터로 복호화 블록 모드 인스턴스 생성
func (x *cbcDecrypter) CryptBlocks(dst, src []byte) //복호화 블록 모드 인스턴스로 복호화

 

#io 패키지

func ReadFull(r Reader, buf []byte)(n int, err error) //io.Reader에서 buf의 길이만큼 데이터를 읽음

 

너무 길어서 코드만 복붙

package main

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"fmt"
	"io"
)

func encrypt(b cipher.Block, plaintext []byte) []byte {
	if mod := len(plaintext) % aes.BlockSize; mod != 0 {
		padding := make([]byte, aes.BlockSize-mod)
		plaintext = append(plaintext, padding...)
	}
	ciphertext := make([]byte, aes.BlockSize+len(plaintext))

	iv := ciphertext[:aes.BlockSize]
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
		fmt.Println(err)
		return nil
	}
	mode := cipher.NewCBCEncrypter(b, iv)
	mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)
	return ciphertext
}

func decrypt(b cipher.Block, ciphertext []byte) []byte {
	if len(ciphertext)%aes.BlockSize != 0 {
		fmt.Println("암호화된 데이터의 길이는 블록 크기의 배수가 되어야 합니다.")
		return nil
	}
	iv := ciphertext[:aes.BlockSize]
	ciphertext = ciphertext[aes.BlockSize:]

	plaintext := make([]byte, len(ciphertext))
	mode := cipher.NewCBCDecrypter(b, iv)

	mode.CryptBlocks(plaintext, ciphertext)

	return plaintext
}

func main() {
	key := "Hello Key 123456" //16바이트
	s := `동해 물과 백두산이 마르고 닳도록
	하느님이 보우하사 우리나라 만세
	무궁화 삼천리 화려강산
	대한 사람, 대한으로 길이 보전하세.`

	block, err := aes.NewCipher([]byte(key))
	if err != nil {
		fmt.Println(err)
		return
	}

	ciphertext := encrypt(block, []byte(s))
	fmt.Printf("%x\n", ciphertext)

	plaintext := decrypt(block, ciphertext)
	fmt.Println(string(plaintext))
}

 실행 결과

- 블록 암호화는 암호화할 데이터의 길이가 블록 크기의 배수여야 한다.

- 모자라는 부분을 패딩으로 채워줌

- 운용방식 : 초기화 벡터(IV)를 첫 블록에 매치, 각 블록은 이전 블록의 암호화 결과와 XOR -> IV는 매번 다른 값으로 생성해야 함

- 복호화할 때 암호화된 데이터의 길이는 블록 크기의 배수가 되어야 함 -> 길이 검사 필요

- CryptBlocks 함수로 복호화

 

3. RSA 공개키 알고리즘

대칭키 알고리즘 - 암호 키가 유출되면 암호화된 데이터를 모두 풀 수 있다는 단점

공개키 알고리즘 - 대칭키 알고리즘에 비해 속도가 느려 대칭키 알고리즘의 암호 키만 공개키 알고리즘으로 암호화하여 네트워크로 전송하기도 함

func GenerateKey(random io.Reader, bits int)(priv *PrivateKey, err error) //개인키와 공개키 생성
func EncryptPKCSv15(rand io.Reader, pub *PublicKey, msg []byte) (out []byte, err error) //평문을 공개키로 암호화
func DecryptPKCSv15(rand io.Reader, priv *PrivateKey, ciphertext []byte) (out []byte, err error) //암호화된 데이터를 개인키로 복호화

 

RSA 암호화&복호화

package main

import (
	"crypto/rand"
	"crypto/rsa"
	"fmt"
)

func main() {
	privateKey, err := rsa.GenerateKey(rand.Reader, 2048) //개인키와 공개키 생성
	if err != nil {
		fmt.Println(err)
		return
	}
	publicKey := &privateKey.PublicKey //개인키 변수 안에 공개키가 들어 있음

	s := `동해 물과 백두산이 마르고 닳도록
	하느님이 보우하사 우리나라 만세.
	무궁화 삼천리 화려강산
	대한 사람, 대한으로 길이 보전하세.`

	ciphertext, err := rsa.EncryptPKCS1v15(
		rand.Reader,
		publicKey,
		[]byte(s),
	)
	fmt.Printf("%x\n", ciphertext)

	plaintext, err := rsa.DecryptPKCS1v15(
		rand.Reader,
		privateKey,
		ciphertext,
	)
	fmt.Println(string(plaintext))
}

 실행결과

- 암호화할 때는 공개키를 사용, 복호화할 때는 개인키를 사용

- 개인키는 외부에 노출할 일 x, 공개키로는 개인키를 추출하기 어렵기 때문에 안전

 

RSA 메시지 서명, 인증

#crypto/rsa 패키지

func SignPKCS1v15(rand io.Reader, priv *PrivateKey, hash crypto.Hash, hashed []byte) (s []byte, err error) //개인키로 서명
func VerifyPKCS1v15(pub *PublicKey, hash crypto.Hash, hashed []byte, sig []byte) (err error) //공개키로 서명 검증

 

#crypto/md5 패키지

func New() hash.Hase //MD5 해시 인스턴스 생성
func (d *digest)Write(p []byte)(nn int, err error) //해시 인스턴스에 데이터 추가
func (d0 *digest) Sum(in []byte) []byte //해시 인스턴스에 저장된 데이터의 MD5 해시 값 추출

 

package main

import (
	"crypto"
	"crypto/md5"
	"crypto/rand"
	"crypto/rsa"
	"fmt"
)

func main() {
	privateKey, err := rsa.GenerateKey(rand.Reader, 2048) //개인키와 공개키 생성
	if err != nil {
		fmt.Println(err)
		return
	}
	publicKey := &privateKey.PublicKey //개인키 변수 안에 공개키가 들어 있음

	message := "안녕하세요. Go 언어"
	hash := md5.New()
	hash.Write([]byte(message))
	digest := hash.Sum(nil)

	var h1 crypto.Hash
	signature, err := rsa.SignPKCS1v15(
		rand.Reader,
		privateKey,
		h1,
		digest,
	)

	var h2 crypto.Hash
	err = rsa.VerifyPKCS1v15(
		publicKey,
		h2,
		digest,
		signature,
	)
	if err != nil {
		fmt.Println("검증 실패")
	} else {
		fmt.Println("검증 성")
	}

}

실행 결과

- rsa.SignPKCS1v15 함수에 rand.Reader, crypto.Hash, 개인키, 해시 값을 넣으면 서명 (h1)

- rsa.VerifyPKCS1v15 함수에 공개키, crypto.Hash, 해시 값, 서명 넣으면 검증 (h2)

- 메시지, 메시지에 대한 해시 값, 서명, 공개키는 모두 공개된 정보

 

UNIT 54 :: 정렬 활용하기


#sort 패키지

func Sort(data Interface) //데이터를 오름차순으로 정렬
func Reverse(data Interface) Interface //내림차순
type IntSlice []int //int 정렬 인터페이스
type Float64Slice []float64 //float64 정렬 인터페이스
type StringSlice []string //string 정렬 인터페이스

- 자료형에 맞게 정렬 인터페이스를 사용 (sort.IntSlice..)

실행결과

 

- 기본 자료형 (정수, 실수, 문자열)들은 슬라이스를 바로 넣을 수 있는 함수 제공

 -> sort.Ints(a), sort.Float64s(a), sort.Strings(c)

 

1. 구조체 정렬

정렬 인터페이스의 정의 (srot.Interface)

type Interface interface{
	 Len() int //데이터의 개수(길이) 구함
     Less(i,j int) bool //대소관계 판단
     Swap(i,j int) //less 메서드에서 true가 나오면 두 데이터의 위치 바꿈
}

- 구조체가 담긴 슬라이스를 정렬하기 위해서 정렬 인터페이스의 정의가 필요함

- ByScore 구조체에 Students 타입을 포함시켜 기존 메서드를 그대로 따름 

- 점수를 기준으로 정렬해야 하기에 Less 메서드를 다시 구현 (상속, 확장)

package main

import (
	"fmt"
	"sort"
)

type Student struct {
	name  string
	score float32
}

type Students []Student

func (s Students) Len() int {
	return len(s) //데이터 길이 구하기
}

func (s Students) Less(i, j int) bool {
	return s[i].name < s[j].name //학생 이름순으로 정렬
}

func (s Students) Swap(i, j int) {
	s[i], s[j] = s[j], s[i] //두 데이터의 위치를 바꿈
}

type ByScore struct { //점수순 정렬을 위해 구조체 정
	Students
}

func (s ByScore) Less(i, j int) bool {
	return s.Students[i].score < s.Students[j].score //학생 이름순으로 정렬
}

func main() {
	s := Students{
		{"maria", 89.3},
		{"andrew", 72.6},
		{"john", 93.1},
	}
	sort.Sort(s)
	fmt.Println(s)

	sort.Sort(sort.Reverse(ByScore{s}))
	fmt.Println(s)
}

실행 결과

 

2. 정렬 키로 정렬

package main

import (
	"fmt"
	"sort"
)

type Student struct {
	name  string
	score float32
}

type By func(s1, s2 *Student) bool //각 상황별 정렬 함수를 저장할 타입
func (by By) Sort(students []Student) {
	sorter := &studentSorter{
		students: students,
		by:       by,
	}
	sort.Sort(sorter)
}

type studentSorter struct {
	students []Student
	by       func(s1, s2 *Student) bool //각 상황별 정렬 함수
}

func (s *studentSorter) Len() int {
	return len(s.students)
}

func (s *studentSorter) Less(i, j int) bool {
	return s.by(&s.students[i], &s.students[j])
}

func (s *studentSorter) Swap(i, j int) {
	s.students[i], s.students[j] = s.students[j], s.students[i]
}

func main() {
	s := []Student{
		{"maria", 89.3},
		{"andrew", 72.6},
		{"john", 93.1},
	}
	name := func(p1, p2 *Student) bool {
		return p1.name < p2.name
	}
	score := func(p1, p2 *Student) bool {
		return p1.score < p2.score
	}
	reverseScore := func(p1, p2 *Student) bool {
		return !score(p1, p2)
	}

	By(name).Sort(s)
	fmt.Println(s)

	By(reverseScore).Sort(s)
	fmt.Println(s)

}

 - By : *Student ㅌ입의 매개변수 s1, s2를 받고 bool를 리턴하는 함수 타입. 실제 함수 x

 - name, score, reverseScore : 정렬 키 함수

- 하나의 데이터 타입을 여러 인터페이스로 바꿔가면서 OOP를 구현 (객체 지향 프로그래밍)

 

실행결과

 

UNIT 55 :: 컨테이너 사용하기


자료구조 - 연결 리스트, 힙, 링

 

1. 연결 리스트

#container/list 패키지

func New() *List //연결리스트 생성
func(I *List) PushBack(v interface{}) *Element //연결리스트의 맨 뒤에 데이터 추가
func(I *List) Front() *Element //연결 리스트의 맨 앞 데이터를 가져옴
func(I *List) Back() *Element //연결 리스트의 맨 뒤 데이터를 가져옴

Go 언어의 연결 리스트는 이중 연결 리스트 -> 양방향 순회 가능

 

실행 화면

연결 리스트 함수 : PushBack, PushFront, PushBackList,...

 

2. 힙

func Init(h Interface) //힙 초기화
func Push(h Interface, x interface{}) //힙에 데이터 추가

- 최대 힙 : 부모 노드의 값이 자식 노드의 값보다 큰 힙

- 최소 힙 : 부모 노드의 값이 자식 노드의 값보다 작은 힙

- heap.Interface 구현 필수

 

최소 힙

package main

import (
	"container/heap"
	"fmt"
)

type MinHeap []int //힙을 int 슬라이스로 정의

func (h MinHeap) Len() int {
	return len(h) //슬라이스의 길이를 구함
}

func (h MinHeap) Less(i, j int) bool {
	r := h[i] < h[j] //대소관계 판단
	fmt.Printf("Less %d < %d %t\n", h[i], h[j], r)
	return r
}

func (h MinHeap) Swap(i, j int) {
	fmt.Printf("Swap %d %d\n", h[i], h[j])
	h[i], h[j] = h[j], h[i] //값의 위치를 바꿈
}

func (h *MinHeap) Push(x interface{}) {
	fmt.Println("Push", x)
	*h = append(*h, x.(int)) //맨 마지막에 값 추가
}

func (h *MinHeap) Pop() interface{} {
	old := *h
	n := len(old)
	x := old[n-1]     //슬라이스의 맨 마지막 값을 가져욤
	*h = old[0 : n-1] //맨 마지막 값을 제외한 슬라이스를 다시 저장
	return x
}

func main() {
	data := new(MinHeap) //힙 생성

	heap.Init(data) //힙 초기화
	heap.Push(data, 5)
	heap.Push(data, 2)
	heap.Push(data, 7)
	heap.Push(data, 3)

	fmt.Println(data, "최솟값 : ", (*data)[0])
}

heap.Push 함수로 값을 넣을 때마다 정렬

 

3. 링

func New(n int) *Ring //링 생성
func (r *Ring) Do(f func(interface{}))) //링의 모든 노드 순회
func (r *Ring) Move(n int) *Ring //링을 회전시킴. 매개변수로 양수를 넣으면 시계방향 회전

- 원형으로 연결된 이중 연결 리스트. 

- 처음과 끝 존재 x

- nil을 가리키는 노드 존재 x'

 

- 뒤로 가든 앞으로 가든 반드시 r.Next()처럼 현재 위치를 변경해주어야 함

 

 

UNIT 56 :: TCP 프로토콜 사용하기


server 코드

#net 패키지

func Listen(net, laddr string) (Listener, error) //프로토콜, IP 주소, 포트 번호를 설정하여 네트워크 연결 대기
func (I *TCPListener) Accept() (Conn, error) //클라이언트가 연결되면 tcp 연결을 리턴
func (I *TCPListener) Close() error //tcp 연결 대기를 닫음
func (c * TCPConn) Read(b []byte) (int, error) //받은 데이터를 읽음
func (c *TCPConn) Write(b []byte) (int,error) //데이터를 보냄
func (c *TCPConn) Close() error //tcp 연결을 닫음

 

경로 : C:\Users\조수경\hello_project\src\tcpserver

package main

import (
	"fmt"
	"net"
)

func requestHandler(c net.Conn) {
	data := make([]byte, 4096)

	for {
		n, err := c.Read(data) //클라이언트에서 받은 데이터를 읽음
		if err != nil {
			fmt.Println(err)
			return
		}
		fmt.Println(string(data[:n]))
		_, err = c.Write(data[:n]) //클라이언트로 데이터를 보냄
		if err != nil {
			fmt.Println(err)
			return
		}
	}
}

func main() {
	ln, err := net.Listen("tcp", ":8000")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer ln.Close()

	for {
		conn, err := ln.Accept()
		if err != nil {
			fmt.Println(err)
			continue
		}
		defer conn.Close()

		go requestHandler(conn)
	}
}

- 포트 번호만 설정 : 모든 네트워크 인터페이스의 ip주소에서 연결을 받음

- ip 주소와 함께 설정 : 특정 nic에서만 tcp 연결을 받음

결과 화면

client 코드

func Dial(network, address string) (Conn, error) //프로토콜, IP 주소, 포트 번호를 설정하여 서버에 연결
func (c *TCPConn) Close() error //tcp 연결 닫음
func (c *TCPConn) Read(b []byte)(int,error) //받은 데이터 읽음
func (c *TCPConn) Write(b []byte)(int,error) //데이터 보냄

 

경로 : C:\Users\조수경\hello_project\src\tcpclient

 

package main

import (
	"fmt"
	"net"
	"strconv"
	"time"
)

func main() {
	client, err := net.Dial("tcp", "127.0.0.1:8000")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer client.Close()

	go func(c net.Conn) {
		data := make([]byte, 4096)
		for {
			n, err := c.Read(data)
			if err != nil {
				fmt.Println(err)
				return
			}
			fmt.Println(string(data[:n]))

			time.Sleep(1 * time.Second)
		}
	}(client)

	go func(c net.Conn) {
		i := 0
		for {
			s := "Hello" + strconv.Itoa(i)

			_, err := c.Write([]byte(s)) //서버로 데이터를 보냄
			if err != nil {
				fmt.Println(err)
				return
			}

			i++
			time.Sleep(1 * time.Second)
		}
	}(client)

	fmt.Scanln()
}

 각각의 폴더에서 exe 파일을 실행해주면 다음과 같은 결과가 나온다. 통신에 성공한 것을 알 수 있다.

 

UNIT 57 :: RPC 프로토콜 사용하기


RPC : 원격에서 함수를 실행하는 기술

 

1. 서버

경로 : C:\Users\조수경\hello_project\src\rpcserver

 

# net/rpc 패키지

func Register(rcvr interface{})error //rpc로 사용할 함수 등록

 

rpcserver 코드 : 덧셈 함수 호출, 두 수 더하기

package main

import (
	"fmt"
	"net"
	"net/rpc"
)

type Calc int //rpc 서버에 등록하기 위해 임의의 타입으로 정의
type Args struct {
	A, B int
}
type Reply struct {
	C int
}

func (c *Calc) Sum(args Args, reply *Reply) error {
	reply.C = args.A + args.B //두값을 더하여 리턴값 구조체에 넣어줌
	return nil
}

func main() {
	rpc.Register(new(Calc)) //calc 타입의 인스턴스 생성, rpc 서버에 등록
	ln, err := net.Listen("tcp", ":6000")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer ln.Close()

	for {
		conn, err := ln.Accept()
		if err != nil {
			continue
		}
		defer conn.Close()

		go rpc.ServeConn(conn)
	}
}

- rpc 서버에 함수를 등록하려면 구조체나 일반 자료형과 같은 타입에 메서드 형태로 구성되어 있어야 함 ex) Cacl 타입을 int 형으로 정의

- tcp 프로토콜 사용

 

2. 클라이언트

경로 : C:\Users\조수경\hello_project\src\rpcclient

 

# net/rpc 패키지

func Dial(network, address string) (*Client,error) //프로토콜, ip주소, 포트 번호를 설정하여 rpc 서버에 연결
func (client *Client) Call(serviceMethod string, args interface{}, reply interface{}) error //rpc 서버의 함수를 호출(동기)
func (client *Client) Go(serviceMethod string, args interface{}, reply interface{},done chan *Call) *Call//rpc 서버의 함수를 고루틴으로 호출(비동기)

client 코드

package main

import (
	"fmt"
	"net/rpc"
)

type Args struct {
	A, B int
}

type Reply struct {
	C int
}

func main() {
	client, err := rpc.Dial("tcp", "127.0.0.1:6000")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer client.Close()

	//동기 호출
	args := &Args{1, 2}
	reply := new(Reply)
	err = client.Call("Calc.Sum", args, reply)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(reply.C)

	//비동기 호출
	args.A = 4
	args.B = 9
	sumCall := client.Go("Calc.Sum", args, reply, nil)
	<-sumCall.Done
	fmt.Println(reply.C)
}

- rpc 서버의 함수를 호출할 때 사용할 매개변수와 리턴 값 구조체는 서버와 동일해야 함

 

서버를 실행한 뒤, 클라이언트를 실행하면 더한 값이 나오게 된다.

client 실행 결과

 

UNIT 58 :: HTTP 서버 사용하기


#net/http 패키지

func ListenAndServe(addr string, handler Handler) error //http 연결을 받고, 요청에 응답
func HandleFunc(pattern string, handler func(ResponseWirter, *Request)) //경로별로 요청을 처리할 핸들러 함수를 등록
func Handle(pattern string, handler Handler) //경로별로 요청을 처리할 핸들러 함수를 등록
func FileServer(root FileSystem) Handler //파일 서버 핸들러
func StripPrefix(prefix string, h Handler) Handler //http 요청 경로에서 특정 문자열을 제거
func NewServeMux() *ServeMux //http 요청 멀티플렉서 인스턴스 생성

 

1. server 코드

경로 : C:\Users\조수경\hello_project\src\httpserver

package main

import (
	"net/http"
)

func main() {
	s := "hello, world!"

	http.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) {
		html := `
		<html>
		<head>
			<title>Hello</title>
			<script type="text/javascript" src="/assets/hello.js"></script>
			<link href="/assets/hello.css" rel="stylesheet"/>
		</head>
		<body>
			<span class="Hello">` + s + `</span>
		</body>
		</html>
		`

		res.Header().Set("Content-Type", "text/html")
		res.Write([]byte(html))

	})

	http.Handle(
		"/assets/",
		http.StripPrefix( //dir이 지정했기에 url 경로에서 /assets/ 삭제
			"/assets/", 
			http.FileServer(http.Dir("assets")), //assets 디렉터리 지정
		),
	)
	http.ListenAndServe(":8080", nil)
}

- http.HandleFunc : 웹 브라우저가 접속했을 떄의 메서드, 쿠키, 헤더 .. ,

- http.Handle : js, css 파일 연결 (assets 경로)

 

2. js, css 파일

경로 : C:\Users\조수경\hello_project\src\httpserver\assets

- 창 뜨는 것, 글자 색 (red)

 

UNIT 59 :: 명령줄 옵션 사용하기


명령줄에서 설정한 옵션을 간단하게 사용 가능

 

#os 패키지

var Args []string //명령줄에서 설정한 옵션

현재 실행 파일 출력

 

#flag 패키지

func String(name string, value string, usage string) *string //명령줄 옵션을 받은 뒤 문자열로 저장
func Int(name string, value int, usage string) *int 
func Float64(name string, value float64, usage string) *float64
func Bool(name string, value bool, usage string) *bool
func Parse() //명령줄 옵션의 내용을 각 자료형별로 분석
func NFlag() int//명령줄 옵션의 개수를 리턴
var Usage = func() {} //명령줄 옵션의 기본 사용법 출력

flag 사용 예제

- 타입별로 사용할 수 있는 함수를 준비해놓고, Parse 함수를 실행하여 각 옵션 값을 저장시킨다. nflag 함수로 설정된 옵션의 개수를 알 수 있고, 설정된 옵션이 없다면 flag.Usage 함수로 옵션 사용법을 출력한다. 

 

cmd 창을 켜서 명령어를 입력해 빌드시켜준다.

명령어 : go build -o cmdflag

빌드가 됐다면, 옵션을 입력시켜준다. 입력된 옵션이 출력되는 것을 확인할 수 있다.

 

UNIT 60 :: 에러 처리하기


Println 함수로 출력만 했었지만, 다양한 함수로 에러를 처리하는 방법 존재

 

#fmt 패키지

func Errorf(format string, a...interface{}) error //형식을 지정하여 error 값을 만듦

#log 패키지

func Fatal(v...interface{}) //에러 로그를 출력하고 프로그램을 완전히 종료
func Panic(v...interface{}) //시간과 에러 문자열을 출력한 뒤 패닉을 발생
func Print(v...interface{}) //시간과 에러 메시지를 출력하며 프로그램을 종료시키지 않음

 

* 1일 때만 정상 동작인 코드 : log.Fatal 

- errorf 함수의 사용법은 printf 함수와 같음

- log.Fatal 함수 : 에러 문자열 출력 후 프로그램 완전 종료

- 에러는 출력되지만, 프로그램은 완전히 종료됨

- 정상 종료 : exit code=1

 

* 1일 때만 정상 동작인 코드 : log.Panic 함수

- log.Panic 함수 : 시간과 에러 문자열을 출력한 뒤 패닉 발생 (panic 함수 대체 가능)

 

- 런타임 패닉 발생, 콜스택 출력

 

* 1일 때만 정상 동작인 코드 : recover

- recover 함수 : loog.Panic, panic 함수에서 설정한 에러 메시지를 리턴, 프로그램을 복구

- 프로그램 종료 x, log.Panic 함수에서 설정한 에러 메시지만 출력됨

 

* 1일 때만 정상 동작인 코드 : log.Print 함수

- log.Print 함수 : 시간과 에러 메시지 출력. 프로그램 종료 x.

단, 프로그램의 실행이 끝난 다음 로그 출력

 

1. 에러 타입 만들기

: 에러를 확장하여 좀 더 자세한 에러 내용을 저장하기 위해 사용

 

package main

import (
	"fmt"
	"log"
	"time"
)

type HelloOneError struct {
	time  time.Time
	value int
}

func (e HelloOneError) Error() string {
	return fmt.Sprintf("%v:%d는 1이 아닙니다.", e.time, e.value)
}

func helloOne(n int) (string, error) {
	if n == 1 {
		s := fmt.Sprintln("Hello", n)
		return s, nil
	}
	return "", HelloOneError{time.Now(), n}
}

func main() {
	s, err := helloOne(1)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(s)

	s, err = helloOne(2)
	if err != nil {
		log.Fatal(err)
	}
	//런타임 에러 발생

	fmt.Println(s)

	fmt.Println("Hello, world!")

}

- 에러 타입이 아니더라도 에러 함수 구현하면 에러로 사용가능

 

- error 함수에서 만든 에러 메시지 출력