I am working on a property rental blockchain, with an API and when I make GET requests locally to localhost:8080/contracts I receive the error: "invalid character 'þ' looking for beginning of value". I receive this error in curl and Postman.
Main.go:
package main
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"net/http"
"time"
"github.com/HartleyIntegrity/smartley-chain/api"
"github.com/HartleyIntegrity/smartley-chain/db"
)
func main() {
// Connect to MongoDB
err := db.ConnectMongoDB()
if err != nil {
log.Fatalf("Error connecting to MongoDB: %v", err)
}
// Create a new router
router := api.NewRouter()
// Logging middleware
router.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Create a response logger to log response details
resLogger := &responseLogger{
ResponseWriter: w,
statusCode: http.StatusOK,
}
next.ServeHTTP(resLogger, r)
// Log the request and response details to console and file
logDetails := fmt.Sprintf("%s %s %d %v %s\n", r.Method, r.URL.Path, resLogger.statusCode, time.Since(start), resLogger.body)
fmt.Print(logDetails)
log.Print(logDetails)
})
})
// Recovery middleware to log panics and recover from them
router.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if r := recover(); r != nil {
errMsg := fmt.Sprintf("Panic: %v", r)
log.Println(errMsg)
http.Error(w, errMsg, http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
})
// Add logging to dump the request body to help debug "invalid character 'þ' looking for beginning of value" error
router.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
bodyBytes, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("Error reading request body: %v", err)
}
log.Printf("Request body: %s", string(bodyBytes))
r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
next.ServeHTTP(w, r)
})
})
// Start the server
log.Println("Starting the server on port 8080...")
log.Fatal(http.ListenAndServe(":8080", router))
}
// Define a custom response logger that logs response details
type responseLogger struct {
http.ResponseWriter
statusCode int
body string
}
func (rl *responseLogger) WriteHeader(statusCode int) {
rl.statusCode = statusCode
rl.ResponseWriter.WriteHeader(statusCode)
}
func (rl *responseLogger) Write(body []byte) (int, error) {
rl.body = string(body)
return rl.ResponseWriter.Write(body)
}
Routes.go
package api
import (
"github.com/gorilla/mux"
)
func NewRouter() *mux.Router {
router := mux.NewRouter().StrictSlash(true)
router.HandleFunc("/contracts", createContractHandler).Methods("POST")
router.HandleFunc("/contracts/{id}", getContractHandler).Methods("GET")
router.HandleFunc("/contracts", listContractsHandler).Methods("GET")
router.HandleFunc("/contracts/{id}", updateContractHandler).Methods("PUT")
router.HandleFunc("/contracts/{id}", deleteContractHandler).Methods("DELETE")
return router
}
Handlers.go
package api
import (
"bytes"
"encoding/json"
"log"
"net/http"
"github.com/HartleyIntegrity/smartley-chain/blockchain"
"github.com/HartleyIntegrity/smartley-chain/contracts"
"github.com/gorilla/mux"
)
func respondWithError(w http.ResponseWriter, code int, message string) {
respondWithJSON(w, code, map[string]string{"error": message})
}
func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) {
response, err := json.Marshal(payload)
if err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
w.Write(response)
}
func createContractHandler(w http.ResponseWriter, r *http.Request) {
// Log the request body to help identify any non-UTF characters causing issues
defer r.Body.Close()
buf := new(bytes.Buffer)
buf.ReadFrom(r.Body)
log.Printf("Request body: %s", buf.String())
// Parse the request body to get the contract data
var contractData blockchain.Contract
err := json.NewDecoder(buf).Decode(&contractData)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Create a new contract using the provided data
newContract := contracts.CreateContract(contractData)
// Respond with the newly created contract
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(newContract)
}
func getContractHandler(w http.ResponseWriter, r *http.Request) {
// Get the ID from the URL
vars := mux.Vars(r)
id := vars["id"]
// Get the contract with the provided ID
contract, err := contracts.GetContract(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
// Respond with the contract
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(contract)
}
func listContractsHandler(w http.ResponseWriter, r *http.Request) {
// Log the request body to help identify any non-UTF characters causing issues
defer r.Body.Close()
buf := new(bytes.Buffer)
buf.ReadFrom(r.Body)
log.Printf("Request body: %s", buf.String())
// Get a list of all contracts
contracts, err := contracts.ListContracts()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Respond with the list of contracts
w.Header().Set("Content-Type", "application/json")
buf.Reset()
err = json.NewEncoder(buf).Encode(contracts)
if err != nil {
log.Printf("Error encoding response body: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
responseBody := buf.String()
w.WriteHeader(http.StatusOK)
if _, err := w.Write(buf.Bytes()); err != nil {
log.Printf("Error writing response body: %v", err)
}
log.Printf("Response body: %s", responseBody)
}
func updateContractHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id := vars["id"]
var newContractData contracts.Contract
if err := json.NewDecoder(r.Body).Decode(&newContractData); err != nil {
respondWithError(w, http.StatusBadRequest, err.Error())
return
}
defer r.Body.Close()
contract, err := contracts.UpdateContract(id, newContractData)
if err != nil {
respondWithError(w, http.StatusNotFound, err.Error())
return
}
respondWithJSON(w, http.StatusOK, contract)
}
func deleteContractHandler(w http.ResponseWriter, r *http.Request) {
// Get the ID from the URL
vars := mux.Vars(r)
id := vars["id"]
// Delete the contract with the provided ID
err := contracts.DeleteContract(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
// Respond with a 204 No Content status to indicate that the contract was successfully deleted
w.WriteHeader(http.StatusNoContent)
}
Blockchain.go
package blockchain
import (
"bytes"
"crypto/rand"
"crypto/sha256"
"encoding/gob"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"log"
"time"
)
type Blockchain struct {
Blocks []*Block
}
func CreateBlock(index int, data Contract, prevHash string) *Block {
timestamp := time.Now()
dataBytes := data.ToBytes()
hash := CalculateHash(data, prevHash, timestamp)
return &Block{index, timestamp.Format(time.RFC3339), dataBytes, hash, prevHash}
}
func (bc *Blockchain) AddBlock(contract Contract, prevHash string) {
prevBlock := bc.Blocks[len(bc.Blocks)-1]
dataBytes := prevBlock.Data
var data Contract
err := json.Unmarshal(dataBytes, &data)
if err != nil {
log.Fatal(err)
}
newBlock := CreateBlock(len(bc.Blocks), data, prevBlock.Hash)
bc.Blocks = append(bc.Blocks, newBlock)
}
func CalculateHash(data Contract, prevHash string, timestamp time.Time) string {
record := fmt.Sprintf("%s%d%d%s%s%s%s%f%f%f%t%s%s",
data.ID,
data.StartTime,
data.ContractDuration,
data.EndTime,
data.CurrentStage,
data.PropertyHash,
data.PropertyID,
data.PropertyAddress,
data.RentAmount,
data.DepositAmount,
data.DepositDeductions,
data.ContractSigned,
prevHash,
timestamp.Format(time.RFC3339),
)
h := sha256.New()
h.Write([]byte(record))
hashed := h.Sum(nil)
return hex.EncodeToString(hashed)
}
func GenerateID() string {
uuid := make([]byte, 16)
_, err := rand.Read(uuid)
if err != nil {
log.Fatal(err)
}
return hex.EncodeToString(uuid)
}
func StageCreated() string {
return "Created"
}
func NewBlockchain() *Blockchain {
genesisBlock := CreateGenesisBlock()
return &Blockchain{Blocks: []*Block{genesisBlock}}
}
func (c *Contract) ToBytes() []byte {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
if err := enc.Encode(c); err != nil {
log.Fatalf("failed to encode contract: %v", err)
}
return buf.Bytes()
}
func CreateGenesisBlock() *Block {
contract := Contract{
ID: GenerateID(),
StartTime: time.Now().Unix(),
ContractDuration: 12,
EndTime: time.Now().AddDate(1, 0, 0).Unix(),
CurrentStage: StageCreated(),
PropertyHash: "",
PropertyID: "",
PropertyAddress: "",
RentAmount: 0,
DepositAmount: 0,
DepositDeductions: 0,
IssuesReported: make([]Issue, 0),
NoticesSent: make([]Notice, 0),
ContractSigned: false,
}
block := CreateBlock(0, contract, "")
return block
}
func (bc *Blockchain) ReplaceBlock(oldBlock, newBlock *Block) error {
// Verify that the new block is valid
if !isValidNewBlock(newBlock, oldBlock) {
return errors.New("invalid block")
}
// Replace the old block with the new block
for i, b := range bc.Blocks {
if b.Hash == oldBlock.Hash {
bc.Blocks[i] = newBlock
return nil
}
}
return errors.New("block not found")
}
func isValidNewBlock(newBlock, previousBlock *Block) bool {
// Check that the block indices are consecutive
if newBlock.Index != previousBlock.Index+1 {
return false
}
// Check that the previous block's hash matches the new block's prevHash
if newBlock.PrevHash != previousBlock.Hash {
return false
}
// Check that the new block's hash is valid
newTimestamp, err := time.Parse(time.RFC3339, newBlock.Timestamp)
if err != nil {
return false
}
// Decode the contract data from the block
var data Contract
err = json.Unmarshal(newBlock.Data, &data)
if err != nil {
return false
}
// Check that the new block's hash is valid
if CalculateHash(data, newBlock.PrevHash, newTimestamp) != newBlock.Hash {
return false
}
return true
}
I have tried setting various headers in my request such as content-type application/json etc. and nothing seems to work. I've asked chatgpt several times to try a solution and it just says there is an UTF-8 issue in a JSON response somewhere and I cannot for the life of me figure this out.
Thank you.
Github repo for reference:
The error is happening because on blockchain.go#line22 you're setting the struct bytes, not a JSON text.
func CreateBlock(index int, data Contract, prevHash string) *Block {
timestamp := time.Now()
// This is struct bytes, change to 'json.Marshal(data)'
dataBytes := data.ToBytes()
hash := CalculateHash(data, prevHash, timestamp)
return &Block{index, timestamp.Format(time.RFC3339), dataBytes, hash, prevHash}
}
It should be json.Marshal(data)
.
Later in the code you're parsing bytes, but the content is bytes from a struct, not JSON text.
func ListContracts() ([]Contract, error) {
bc := blockchain.NewBlockchain()
contracts := make([]Contract, 0)
blocks := bc.Blocks
for i := len(blocks) - 1; i >= 0; i-- {
block := blocks[i]
var contract blockchain.Contract
// Error happens here
err := json.Unmarshal(block.Data, &contract)
if err != nil {
return nil, err
}
contracts = append(contracts, Contract{contract})
}
return contracts, nil
}
Then you get the error.
That being said, you should review your approach as carrying out the JSON text in that manner seems unnecessary, but changing that for now should work.
I've sent this PR to your repo if you want to test it from there.