main.go
package main
import (
"coin/exam16/blockchain"
"coin/exam16/utils"
"encoding/json"
"fmt"
"log"
"net/http"
)
const port string = ":4000"
type URL string
func (u URL) MarshalText() ([]byte, error) {
url := fmt.Sprintf("http://localhost%s%s", port, u)
return []byte(url), nil
}
type URLDescription struct {
URL URL `json:"url"`
Method string `json:"method"`
Description string `json:"description"`
Payload string `json:"payload,omitempty"`
}
type AddBlockBody struct {
Message string `json:"message"`
}
func (u URLDescription) String() string {
return "Hello I'm the URL Description"
}
func documentation(rw http.ResponseWriter, r *http.Request) {
data := []URLDescription{
{
URL: URL("/"),
Method: "GET",
Description: "See Documentation",
},
{
URL: URL("/blocks"),
Method: "GET",
Description: "See All Block",
},
{
URL: URL("/blocks"),
Method: "POST",
Description: "Add A Block",
Payload: "data:string",
},
{
URL: URL("/blocks/{id}"),
Method: "GET",
Description: "See A Block",
},
}
rw.Header().Add("Content-Type", "application/json")
json.NewEncoder(rw).Encode(data)
}
func blocks(rw http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
rw.Header().Add("Content-Type", "application/json")
json.NewEncoder(rw).Encode(blockchain.GetBlockchain().AllBlocks())
case "POST":
var addBlockBody AddBlockBody
utils.HandleErr(json.NewDecoder(r.Body).Decode(&addBlockBody))
blockchain.GetBlockchain().AddBlock(addBlockBody.Message)
rw.WriteHeader(http.StatusCreated)
}
}
func main() {
http.HandleFunc("/", documentation)
http.HandleFunc("/blocks", blocks)
fmt.Printf("Listening on http://localhost%s\n", port)
log.Fatal(http.ListenAndServe(port, nil))
}
모든 블록 가져오기
GET
URL : /blocks
HTTP/1.1 200 OK
Content-Type: application/json
Date: Wed, 28 Dec 2022 04:34:24 GMT
Content-Length: 115
Connection: close
[
{
"Data": "Genesis Block",
"Hash": "89eb0ac031a63d2421cd05a2fbe41f3ea35f5c3712ca839cbf6b85c4ee07b7a3",
"PrevHash": ""
}
]
블록 생성 및 추가하기
POST
URL : /blocks
```go
HTTP/1.1 201 Created
Date: Wed, 28 Dec 2022 04:34:51 GMT
Content-Length: 0
Connection: close
```
추가된 블록 확인하기
GET
URL : /Blocks
```go
HTTP/1.1 200 OK
Content-Type: application/json
Date: Wed, 28 Dec 2022 04:35:43 GMT
Content-Length: 477
Connection: close
[
{
"Data": "Genesis Block",
"Hash": "89eb0ac031a63d2421cd05a2fbe41f3ea35f5c3712ca839cbf6b85c4ee07b7a3",
"PrevHash": ""
},
{
"Data": "Data for my block",
"Hash": "6de940f3a7ead5008e358bdda0ac9b0234a4e8dbc94c31ca1dd91b8798607182",
"PrevHash": "89eb0ac031a63d2421cd05a2fbe41f3ea35f5c3712ca839cbf6b85c4ee07b7a3"
},
]
```
package main
import (
"coin/exam17/explorer"
"coin/exam17/rest"
)
func main() {
go explorer.Start(3000)
rest.Start(4000)
}
package rest
import (
"coin/exam17/blockchain"
"coin/exam17/utils"
"encoding/json"
"fmt"
"log"
"net/http"
)
var port string
type url string
func (u url) MarshalText() ([]byte, error) {
url := fmt.Sprintf("http://localhost%s%s", port, u)
return []byte(url), nil
}
type urlDescription struct {
URL url `json:"url"`
Method string `json:"method"`
Description string `json:"description"`
Payload string `json:"payload,omitempty"`
}
type addBlockBody struct {
Message string `json:"message"`
}
func (u urlDescription) String() string {
return "Hello I'm the URL Description"
}
func documentation(rw http.ResponseWriter, r *http.Request) {
data := []urlDescription{
{
URL: url("/"),
Method: "GET",
Description: "See Documentation",
},
{
URL: url("/blocks"),
Method: "GET",
Description: "See All Block",
},
{
URL: url("/blocks"),
Method: "POST",
Description: "Add A Block",
Payload: "data:string",
},
{
URL: url("/blocks/{id}"),
Method: "GET",
Description: "See A Block",
},
}
rw.Header().Add("Content-Type", "application/json")
json.NewEncoder(rw).Encode(data)
}
func blocks(rw http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
rw.Header().Add("Content-Type", "application/json")
json.NewEncoder(rw).Encode(blockchain.GetBlockchain().AllBlocks())
case "POST":
var addBlockBody addBlockBody
utils.HandleErr(json.NewDecoder(r.Body).Decode(&addBlockBody))
blockchain.GetBlockchain().AddBlock(addBlockBody.Message)
rw.WriteHeader(http.StatusCreated)
}
}
func Start(aPort int) {
handler := http.NewServeMux()
port = fmt.Sprintf(":%d", aPort)
handler.HandleFunc("/", documentation)
handler.HandleFunc("/blocks", blocks)
fmt.Printf("Listening on http://localhost%s\n", port)
log.Fatal(http.ListenAndServe(port, handler))
}
package explorer
import (
"coin/exam17/blockchain"
"fmt"
"log"
"net/http"
"text/template"
)
const (
templateDir string = "explorer/templates/"
)
var templates *template.Template
type homeData struct {
PageTitle string
Blocks []*blockchain.Block
}
func home(rw http.ResponseWriter, r *http.Request) {
data := homeData{"Home", blockchain.GetBlockchain().AllBlocks()}
templates.ExecuteTemplate(rw, "home", data)
}
func add(rw http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
templates.ExecuteTemplate(rw, "add", nil)
case "POST":
r.ParseForm()
data := r.FormValue("blockData")
fmt.Println(data)
blockchain.GetBlockchain().AddBlock(data)
http.Redirect(rw, r, "/", http.StatusPermanentRedirect)
}
}
func Start(port int) {
handle := http.NewServeMux()
templates = template.Must(template.ParseGlob(templateDir + "pages/*.gohtml"))
templates = template.Must(templates.ParseGlob(templateDir + "partials/*.gohtml"))
handle.HandleFunc("/", home)
handle.HandleFunc("/add", add)
fmt.Printf("Listening on http://localhost%d\n", port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), handle))
}
package main
import (
"coin/exam18/rest"
)
func main() {
rest.Start(4000)
}
package rest
import (
"coin/exam18/blockchain"
"coin/exam18/utils"
"encoding/json"
"fmt"
"log"
"net/http"
"github.com/gorilla/mux"
)
//
// ...
//
func block(rw http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
fmt.Println(vars)
id := vars["id"]
}
func Start(aPort int) {
router := mux.NewRouter()
port = fmt.Sprintf(":%d", aPort)
router.HandleFunc("/", documentation).Methods("GET")
router.HandleFunc("/blocks", blocks).Methods("GET", "POST")
router.HandleFunc("/blocks/{id:[0-9]+}", block).Methods("GET")
fmt.Printf("Listening on http://localhost%s\n", port)
log.Fatal(http.ListenAndServe(port, router))
}
Listening on http://localhost:4000
map[id:1]
package main
import (
"coin/exam19/rest"
)
func main() {
rest.Start(4000)
}
package rest
import (
"coin/exam19/blockchain"
"coin/exam19/utils"
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
"github.com/gorilla/mux"
)
// ...
func documentation(rw http.ResponseWriter, r *http.Request) {
data := []urlDescription{
// ...
// 블록의 height값을 받는다
{
URL: url("/blocks/{height}"),
Method: "GET",
Description: "See A Block",
},
}
}
// URL의 파라미터를 받아서 해당하는 블록을 찾아 json형태로 출력한다.
func block(rw http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["height"])
utils.HandleErr(err)
block := blockchain.GetBlockchain().GetBlock(id)
json.NewEncoder(rw).Encode(block)
}
func Start(aPort int) {
router := mux.NewRouter()
// ...
router.HandleFunc("/blocks/{height:[0-9]+}", block).Methods("GET")
fmt.Printf("Listening on http://localhost%s\n", port)
log.Fatal(http.ListenAndServe(port, router))
}
package blockchain
// 블록의 데이터에 height 값을 넣어준다.
type Block struct {
Data string `json:"data"`
Hash string `json:"hash"`
PrevHash string `json:"prevhash,omitempty"`
Height int `json:"height"`
}
// ...
// 블록을 생성할 때 height 값도 함께 넣어준다.
func createBlock(data string) *Block {
newBlock := Block{data, "", getLastHash(), len(GetBlockchain().blocks) + 1}
newBlock.calculateHash()
return &newBlock
}
// ...
// height 값을 받아 하나의 블록을 반환한다
func (b *blockchain) GetBlock(height int) *Block {
return b.blocks[height-1]
}
기존에 저장된 블록체인 정보를 가져온다.
[
{
"data": "Genesis Block",
"hash": "89eb0ac031a63d2421cd05a2fbe41f3ea35f5c3712ca839cbf6b85c4ee07b7a3",
"height": 1
}
]
하나의 블록을 생성한다.
HTTP/1.1 201 Created
Date: Wed, 28 Dec 2022 08:17:07 GMT
Content-Length: 0
Connection: close
생성된 블록을 확인한다.
[
{
"data": "Genesis Block",
"hash": "89eb0ac031a63d2421cd05a2fbe41f3ea35f5c3712ca839cbf6b85c4ee07b7a3",
"height": 1
},
{
"data": "Data for my block",
"hash": "6de940f3a7ead5008e358bdda0ac9b0234a4e8dbc94c31ca1dd91b8798607182",
"prevhash": "89eb0ac031a63d2421cd05a2fbe41f3ea35f5c3712ca839cbf6b85c4ee07b7a3",
"height": 2
}
]
height가 2번인 블록의 정보를 가져온다
{
"data": "Data for my block",
"hash": "6de940f3a7ead5008e358bdda0ac9b0234a4e8dbc94c31ca1dd91b8798607182",
"prevhash": "89eb0ac031a63d2421cd05a2fbe41f3ea35f5c3712ca839cbf6b85c4ee07b7a3",
"height": 2
}
package main
import (
"coin/exam20/rest"
)
func main() {
rest.Start(4000)
}
package rest
// 에러 메세지를 저장하기 위한 구조체 선언
type errorResponse struct {
ErrorMessage string `json:"errormessage"`
}
// ...
func block(rw http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["height"])
utils.HandleErr(err)
block, err := blockchain.GetBlockchain().GetBlock(id)
encoder := json.NewEncoder(rw)
if err == blockchain.ErrNotFound {
encoder.Encode(errorResponse{fmt.Sprint(err)})
} else {
encoder.Encode(block)
}
}
// ...
// 에러 메세지를 만든다.
var ErrNotFound = errors.New("block not found")
// Client가 잘못된 인덱스에 접근하면 에러 메세지를 리턴한다.
func (b *blockchain) GetBlock(height int) (*Block, error) {
if height > len(b.blocks) {
return nil, ErrNotFound
}
return b.blocks[height-1], nil
}
HTTP/1.1 200 OK
Date: Wed, 28 Dec 2022 08:41:00 GMT
Content-Length: 35
Content-Type: text/plain; charset=utf-8
Connection: close
{
"errormessage": "block not found"
}
package rest
// ...
func jsonContentTypeMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
rw.Header().Add("Content-Type", "application/json")
next.ServeHTTP(rw, r)
})
}
func Start(aPort int) {
router := mux.NewRouter()
router.Use(jsonContentTypeMiddleware)
port = fmt.Sprintf(":%d", aPort)
router.HandleFunc("/", documentation).Methods("GET")
//...
}
HTTP/1.1 200 OK
Content-Type: application/json
Date: Wed, 28 Dec 2022 15:11:12 GMT
Content-Length: 366
Connection: close