http – How to properly notify a client from the server side in Golang (broadcasting)?

http – How to properly notify a client from the server side in Golang (broadcasting)? – Software Engineering Stack Exchange

go – How to declare something like empty Object/Struct inside Struct in Golang?

Hi how to create empty Object/Struct as domain model data type? i am using Fiber as my framework and here is the example of what i want to get from golang response api is something like this :

{
  "success": true,
  "message": "Success registered",
  "data": {
      "first_name": "asdfa",
      "last_name": "asdfa",
      "email": "fadsfa",
      "password": "asdfasfas"
  }
}

and here is how i try, here is my responseDto.go :

type ResponseDto struct {
    Success bool   `json:"success"`
    Message string   `json:"message"`
    Data    fiber.Map `json:"data"`
}

then here is how my controller looked like :

func Register(c *fiber.Ctx) error {
    user := request.UserRequest{}
    //do something then return

    return c.JSON(models.ResponseDto{
        Success: false,
        Message: "Success registered",
        Data: fiber.Map{
            "data": user,
        },
    })
}

then here is the response :

{
  "success": false,
  "message": "Success registered",
  "data": {
    "data": {
      "first_name": "asdfa",
      "last_name": "asdfa",
      "email": "fadsfa",
      "password": "asdfasfas"
    }
  }
}

as you can see, i will have nested data, but i don’t want it to be nested, i want it just one level nested, like my example . How can i achieve it? what data type should i use and how to use it to assign the value?

cython vs golang for resource intensive tasks

my question stems from an older question which I asked a while ago. now the there is consensus among team to explore using cython or golang or something better which has good concurrency support. we have some python custom auth module which we would like to reuse.

so my question is,

  1. what language in your opinion is suited for the task given at hand?
  2. what other languages modules are available where we can use existing python custom auth?
  3. does language choice really matter?

multithreading – How to properly implement a http endpoint processing in Golang

I am currently building an API to do some HTTP endpoint processing in Go. I have a snippet already prepared, I need some insights on how to make it better built then possibly if there are issues in the code I should address, please indicate as well. Golang users, please review and advise:

package endpoints


import (
    "database/sql"
    "net/http"
    "os"
    "path"

    echo "github.com/labstack/echo/v4"
)

var dbURL = os.Getenv("DB_URL")

func AddRoutes(g *echo.Group) {
    g.GET("/:id/info", getDatasetMeta)
}

func getDatasetMeta(c echo.Context) error {
    if ok, err := authenticateRequest(c.Request()); err != nil {
        return err
    } else if !ok {
        return echo.NewHTTPError(http.StatusForbidden)
    }

    id := c.Param("id")

    dbResp, err := http.Get(path.Join(dbURL, "datasets", id, "info"))
    if err != nil {
        return err
    }

    return c.Stream(dbResp.StatusCode, dbResp.Header.Get("Content-Type"), dbResp.Body)
}

func authenticateRequest(r *http.Request) (bool, error) {
    sessCookie, err := r.Cookie("session_id")
    if err != nil {
        return false, err
    }

    db, err := getDBConn()
    if err != nil {
        return false, err
    }

    row := db.QueryRowContext(r.Context(), "SELECT * FROM sessions WHERE cookie = ?", sessCookie.Value)
    if err = row.Scan(); err != nil {
        return false, err
    }

    return true, nil
}

func getDBConn() (*sql.DB, error) {
    }

The code is to process http endpoint and please see the following concerns:

  • How can I add execution scheduling in this snippet?
  • Is there a better/more effective way to achieve this task?
  • Where does testing packaging come in with this snippet?
  • Other observations you may have?

. I am new to Go and would really appreciate some comments. Thanks a million!

go – Parsing ssh key with ssh golang package

I am trying to use the ParseRawPrivateKey() from the ssh package of golang (https://pkg.go.dev/golang.org/x/crypto/ssh#ParseRawPrivateKey). The documentation states that it supports both PKCS1 and PKCS8 types. I have 2 types of keys:

  1. PRIVATE KEY (PKCS8)
  2. RSA PRIVATE KEY (PKCS1)

But I am getting this following error with PKCS8 type. It works perfectly fine with “RSA PRIVATE KEY” type:

ssh: unsupported key type "PRIVATE KEY"

Please let me know why this isn’t working and help me in parsing both the type of keys.

Sign lightning channel creation PSBT with Golang

I’m trying to open a channel on regtest with LND v0.13.0-beta.rc3 using their PSBT workflow to allow for remote signing.

I’ve done the following steps:

  1. I create a regtest wallet using the BIP39 mnemonic bronze execute spirit sense rack this repeat access crash train elder purpose rent gain limb view gossip say best desert uncover jaguar exhaust card
  2. I import the extended public key for my account into LND
    seed, err := bip39.MnemonicToByteArray(wallet.Mnemonic, true)
    if err != nil {
        return err
    }

    // Generate a new master node using the seed.
    prvkey, err := hdkeychain.NewMaster(seed, &chaincfg.RegressionNetParams)
    if err != nil {
        return err
    }

    for i := 0; i < 3; i++ {
        prvkey, err = prvkey.Derive(hdkeychain.HardenedKeyStart)
        if err != nil {
            return err
        }
    }

    key, err := prvkey.Neuter()
    if err != nil {
        return err
    }

    importReq := &walletrpc.ImportAccountRequest{
        Name:                 "test",
        ExtendedPublicKey:    key.String(),
        MasterKeyFingerprint: writeUint32(prvkey.ParentFingerprint()),
        AddressType:          1,
        DryRun:               false,
    }
    res, err := m.client.ImportAccount(ctx, importReq)
lnd@alice:/$ lncli wallet accounts list
{
    "accounts": (
        {
            "name": "default",
            "address_type": "HYBRID_NESTED_WITNESS_PUBKEY_HASH",
            "extended_public_key": "upub5Dbp2w1Q7VPuhHQhPtq6rJj7wy8bbEdjFyn1TuHg6MmoSTvR3biPji1nvMRAeoyskhBUBNGGFhzUBm5KQ51M24nKcoxQ7ioFuFeyuiWqk8p",
            "master_key_fingerprint": null,
            "derivation_path": "m/49'/0'/0'",
            "external_key_count": 0,
            "internal_key_count": 0,
            "watch_only": false
        },
        {
            "name": "default",
            "address_type": "WITNESS_PUBKEY_HASH",
            "extended_public_key": "vpub5YyjQMoehZ9g6gu4sQ1VdBTwwMfgfBsPzViqWjMACu1r1JcFkky2PRyYmrGNtgQZh2BY9ZprnRoUjpFm2cx9mPiJ9NhZZ7uCZgQ8x4UNay5",
            "master_key_fingerprint": null,
            "derivation_path": "m/84'/0'/0'",
            "external_key_count": 1,
            "internal_key_count": 3,
            "watch_only": false
        },
        {
            "name": "test",
            "address_type": "WITNESS_PUBKEY_HASH",
            "extended_public_key": "tpubDDMrn46y7HdxK2AHN1anVxj8gSiYLXtmZ4Jyn1Q9Va7AZt5XBdbQ8CUms5NRv6eXBGA8FsJTcEk6h3Wihb49BYMxMqM6k83FWYCpAFLN7Rh",
            "master_key_fingerprint": null,
            "derivation_path": "m/84'/0'/0'",
            "external_key_count": 2,
            "internal_key_count": 35,
            "watch_only": true
        }
    )
}
  1. I call the LND gRPC endpoint Lighting.OpenChannel with a Psbt FundingShim and receive the empty PSBT
  2. I call WalletKit.FundPsbt to fund the PSBT with the test account and get the following PSBT
cHNidP8BAH0CAAAAAf1dlbtrkfmWZasLXxXBEOZbqL5bNT7xONtdc2zTvSRdAAAAAAD/////AoAaBgAAAAAAIgAgLXNsa/AXkklm/LtR6cRgyY0R3TjzCv4OpA0zK7Rc0pNmqAoAAAAAABYAFJrmQkFPaRSt6hfN8U4KFByZXIw3AAAAAAABAN4BAAAAAAEBvvrMn8zVi8D0+0mKi3TGtP8OltDfymQNbfrF1PvEvMIBAAAAAP////8C4MgQAAAAAAAWABQKYpfN3bKI+eLmUec/pPAHC5aUB4Gc1QUAAAAAFgAUOk/UwylhTSOS9zMFLinyDOpn3m4CRzBEAiB11J75wXdm8VggN5MAD2FfG6oRVwAOge+V7XVC3cv34QIgE11HHRZuFZnqaTwSsgycVSHemWoC4GjgXQcO3+odgBcBIQNThPBjoshiGPI00vjOkMJ2pFgb6cvt4qOnOn2FQhDeRgAAAAABAR/gyBAAAAAAABYAFApil83dsoj54uZR5z+k8AcLlpQHAQMEAQAAACIGAzR2EGePj6pLqU6hqkgFrayXL+8s6cj42Q118v0X0CYcGAAAAABUAACAAAAAgAAAAIAAAAAAAAAAAAAAAA==

which bitcoin-cli decodes to

{
  "tx": {
    "txid": "23cec773304f28bb78fdd88b4ad31e3b5f4331b83b7106831f1a4e182e3449a4",
    "hash": "23cec773304f28bb78fdd88b4ad31e3b5f4331b83b7106831f1a4e182e3449a4",
    "version": 2,
    "size": 125,
    "vsize": 125,
    "weight": 500,
    "locktime": 0,
    "vin": (
      {
        "txid": "5d24bdd36c735ddb38f13e355bbea85be610c1155f0bab6596f9916bbb955dfd",
        "vout": 0,
        "scriptSig": {
          "asm": "",
          "hex": ""
        },
        "sequence": 4294967295
      }
    ),
    "vout": (
      {
        "value": 0.00400000,
        "n": 0,
        "scriptPubKey": {
          "asm": "0 e9c4eaee376e5961d61e73fa5e855f224b31037bab32de63ec83ced969c4dcf5",
          "hex": "0020e9c4eaee376e5961d61e73fa5e855f224b31037bab32de63ec83ced969c4dcf5",
          "reqSigs": 1,
          "type": "witness_v0_scripthash",
          "addresses": (
            "bc1qa8zw4m3hdevkr4s7w0a9ap2lyf9nzqmm4veduclvs08dj6wymn6sgq85mn"
          )
        }
      },
      {
        "value": 0.00698470,
        "n": 1,
        "scriptPubKey": {
          "asm": "0 aafc3223cb003a5077badd7af66cab8dd0576976",
          "hex": "0014aafc3223cb003a5077badd7af66cab8dd0576976",
          "reqSigs": 1,
          "type": "witness_v0_keyhash",
          "addresses": (
            "bc1q4t7ryg7tqqa9qaa6m4a0vm9t3hg9w6tkk89wnz"
          )
        }
      }
    )
  },
  "unknown": {
  },
  "inputs": (
    {
      "witness_utxo": {
        "amount": 0.01100000,
        "scriptPubKey": {
          "asm": "0 0a6297cdddb288f9e2e651e73fa4f0070b969407",
          "hex": "00140a6297cdddb288f9e2e651e73fa4f0070b969407",
          "type": "witness_v0_keyhash",
          "address": "bc1qpf3f0nwak2y0nchx28nnlf8squ9ed9q88sfrrr"
        }
      },
      "non_witness_utxo": {
        "txid": "5d24bdd36c735ddb38f13e355bbea85be610c1155f0bab6596f9916bbb955dfd",
        "hash": "4b84437691dbc3ed4c4e6cb1946ebefe7f26147d6d42f25aea73582f20049800",
        "version": 1,
        "size": 222,
        "vsize": 141,
        "weight": 561,
        "locktime": 0,
        "vin": (
          {
            "txid": "c2bcc4fbd4c5fa6d0d64cadfd0960effb4c6748b8a49fbf4c08bd5cc9fccfabe",
            "vout": 1,
            "scriptSig": {
              "asm": "",
              "hex": ""
            },
            "txinwitness": (
              "3044022075d49ef9c17766f158203793000f615f1baa1157000e81ef95ed7542ddcbf7e10220135d471d166e1599ea693c12b20c9c5521de996a02e068e05d070edfea1d801701",
              "035384f063a2c86218f234d2f8ce90c276a4581be9cbede2a3a73a7d854210de46"
            ),
            "sequence": 4294967295
          }
        ),
        "vout": (
          {
            "value": 0.01100000,
            "n": 0,
            "scriptPubKey": {
              "asm": "0 0a6297cdddb288f9e2e651e73fa4f0070b969407",
              "hex": "00140a6297cdddb288f9e2e651e73fa4f0070b969407",
              "reqSigs": 1,
              "type": "witness_v0_keyhash",
              "addresses": (
                "bc1qpf3f0nwak2y0nchx28nnlf8squ9ed9q88sfrrr"
              )
            }
          },
          {
            "value": 0.97885313,
            "n": 1,
            "scriptPubKey": {
              "asm": "0 3a4fd4c329614d2392f733052e29f20cea67de6e",
              "hex": "00143a4fd4c329614d2392f733052e29f20cea67de6e",
              "reqSigs": 1,
              "type": "witness_v0_keyhash",
              "addresses": (
                "bc1q8f8afsefv9xj8yhhxvzju20jpn4x0hnwgnx7ny"
              )
            }
          }
        )
      },
      "sighash": "ALL",
      "bip32_derivs": (
        {
          "pubkey": "03347610678f8faa4ba94ea1aa4805adac972fef2ce9c8f8d90d75f2fd17d0261c",
          "master_fingerprint": "00000000",
          "path": "m/84'/0'/0'/0/0"
        }
      )
    }
  ),
  "outputs": (
    {
    },
    {
    }
  ),
  "fee": 0.00001530
}
  1. I attempt to sign the transaction using code adapted from this SO thread.
func (w *RemoteWallet) FinalizePsbt(psbtBytes ()byte) (()byte, error) {
    reader := bytes.NewReader(psbtBytes)
    packet, err := psbt.NewFromRawBytes(reader, false)
    if err != nil {
        return nil, err
    }

    // Load the extended private key.
    seed, err := bip39.MnemonicToByteArray(w.Mnemonic, true)
    if err != nil {
        return nil, err
    }

    // Generate a new master node using the seed.
    bip32Key, err := hdkeychain.NewMaster(seed, &chaincfg.RegressionNetParams)
    if err != nil {
        return nil, err
    }
    log.Printf("key1 %s", bip32Key.String())

    for i := 0; i < 3; i++ {
        bip32Key, err = bip32Key.Derive(hdkeychain.HardenedKeyStart)
        if err != nil {
            return nil, err
        }
    }

    key, err := bip32Key.Neuter()
    if err != nil {
        return nil, err
    }
    log.Printf("depth %d", key.Depth())
    log.Printf("key2 %s", key.String())

    log.Println("FinalizePsbt Done Loading")

    for idx, input := range packet.Inputs {
        // Derivation path should be read from PSBT.
        // Note: We ignore checking the fingerprint, etc.
        path := input.Bip32Derivation(0)
        log.Printf("input path: %v", path.Bip32Path)
        inputKey := bip32Key
        for _, d := range path.Bip32Path {
            inputKey, _ = inputKey.Derive(d)
        }
        pubkey, err := inputKey.Neuter()
        if err != nil {
            return nil, err
        }
        log.Printf("depth %d", pubkey.Depth())
        log.Printf("input key %s", pubkey.String())

        pubKey, err := bip32Key.ECPubKey()
        if err != nil {
            return nil, err
        }
        privKey, err := bip32Key.ECPrivKey()
        if err != nil {
            return nil, err
        }

        log.Printf("SighashType: %v", input.SighashType)
        log.Printf("Witness script: %v", input.WitnessScript)

        sigHashes := txscript.NewTxSigHashes(packet.UnsignedTx)

        sig, err := txscript.RawTxInWitnessSignature(packet.UnsignedTx, sigHashes, idx,
            input.WitnessUtxo.Value, input.WitnessUtxo.PkScript,
            txscript.SigHashAll, privKey)

        if err != nil {
            return nil, err
        }
        log.Println("Signing input")

        // Use the Updater to add the signature to the input.
        u, err := psbt.NewUpdater(packet)
        if err != nil {
            return nil, err
        }
        success, err := u.Sign(idx, sig, pubKey.SerializeCompressed(), nil, input.WitnessScript)
        if err != nil {
            return nil, err
        }
        if success != psbt.SignSuccesful {
            return nil, fmt.Errorf("could not sucessfully sign psbt")
        }

        log.Println("Signed input")
    }
    // Finalize PSBT.
    err = psbt.Finalize(packet, 0)
    if err != nil {
        return nil, err
    }

    tx, err := psbt.Extract(packet)
    if err != nil {
        return nil, err
    }

    var buf bytes.Buffer
    err = tx.Serialize(&buf)
    return buf.Bytes(), err
}

This outputs

2021/06/28 14:36:35 key1 tprv8ZgxMBicQKsPeHPJRNuvXzDo2U86CLouVfExiLeiTrcffR9aDRC3zMLTPHohQhEFSQNvCAmVeX7SXGS5te7pfdLXjWHsaaLGL3gsktdGziG
2021/06/28 14:36:35 depth 3
2021/06/28 14:36:35 key2 tpubDDMrn46y7HdxK2AHN1anVxj8gSiYLXtmZ4Jyn1Q9Va7AZt5XBdbQ8CUms5NRv6eXBGA8FsJTcEk6h3Wihb49BYMxMqM6k83FWYCpAFLN7Rh
2021/06/28 14:36:35 FinalizePsbt Done Loading
2021/06/28 14:36:35 input path: (2147483732 2147483648 2147483648 0 0)
2021/06/28 14:36:35 depth 5
2021/06/28 14:36:35 input key tpubDFuWC8gxvpmEMQMhsr5vhE1FviMyPKpPTi2dBBMJ9sBvacjeuFbnK6JWULL25e8mXRoTRjVAe1e9GqmiynAFUwKrqKnRVC46bz4ssBRHxmW
2021/06/28 14:36:35 SighashType: 1
2021/06/28 14:36:35 Witness script: ()
2021/06/28 14:36:35 Signing input

before crashing with the error Signature does not correspond to this input.

This is my first time working with PSBTs, so I’m not sure what I’m doing. I’ve been playing around with various parts of the FinalizePsbt func, but without any good resources, it’s been a guessing game.

If I missed any important info, please let me know. Any help is appreciated!

sorting – I recently wrote sort of structs in golang

I am a bit concerned about style (functions, variable names, spacings, etc.). I am also not sure about whether I should return error or panic.
What do you think?

//SortStructs sorts user-made structs, given that "a" is a pointer to slice of structs
//and key's type (which is name of a field by which struct would be sorted) is one of the basic GO types
//which will be sorted in ascending order when asc is true and the other way around, fields must be exported
func SortStructs(a interface{}, key string, asc bool) error {
    var (
        fName    string
        fPos     int
        valSlice ()reflect.Value
    )

    if a == nil {
        return errors.New("mysort: given interface is empty")
    }

    structSlicePointer := reflect.ValueOf(a)
    if structSlicePointer.Kind() != reflect.Ptr {
        return errors.New("mysort: given interface is not pointer")
    } else if structSlicePointer.Elem().Kind() != reflect.Slice {
        return errors.New("mysort: given interface is not pointer to slice")
    }

    //append single structs here
    for i := 0; i < structSlicePointer.Elem().Len(); i++ {
        valSlice = append(valSlice, structSlicePointer.Elem().Index(i))
    }

    if valSlice(0).Kind() != reflect.Struct {
        return errors.New("mysort: interface is not a struct")
    }

    //search for key here
    sl := valSlice(0)
    for ; fPos < sl.NumField(); fPos++ { //range over fields and search for match with key
        fName = sl.Type().Field(fPos).Name
        if fName == key {
            break
        }
    }

    if fPos == sl.NumField() && fName != key {
        return errors.New("mysort: key not found")
    } else if !basicGoType(sl.FieldByName(key)) {
        return errors.New("mysort: key is not a basic go type")
    }

    sortReflect(valSlice, 0, len(valSlice)-1, key, asc)
    return nil
}

func basicGoType(a reflect.Value) bool {
    return a.Kind() == reflect.Bool ||
        a.Kind() == reflect.Int || a.Kind() == reflect.Int8 || a.Kind() == reflect.Int16 || a.Kind() == reflect.Int32 || a.Kind() == reflect.Int64 ||
        a.Kind() == reflect.Uint || a.Kind() == reflect.Uint8 || a.Kind() == reflect.Uint16 || a.Kind() == reflect.Uint32 || a.Kind() == reflect.Uint64 ||
        a.Kind() == reflect.Complex64 || a.Kind() == reflect.Complex128 || a.Kind() == reflect.Float32 || a.Kind() == reflect.Float64 || a.Kind() == reflect.String
}

func aLessThanBReflect(a reflect.Value, b reflect.Value) bool {
    switch a.Kind() {
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        int1 := a.Int()
        int2 := b.Int()
        return int1 < int2
    case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
        uint1 := a.Uint()
        uint2 := b.Uint()
        return uint1 < uint2
    case reflect.Bool:
        bool1 := a.Bool()
        bool2 := b.Bool()
        return !bool1 && bool2
    case reflect.Float32, reflect.Float64:
        float1 := a.Float()
        float2 := b.Float()
        return float1 < float2
    case reflect.Complex64, reflect.Complex128:
        complex1 := a.Complex()
        complex2 := b.Complex()
        if real(complex1) == real(complex2) {
            return imag(complex1) < imag(complex2)
        }
        return real(complex1) < real(complex2)
    case reflect.String:
        str1 := a.String()
        str2 := b.String()
        return str1 < str2
    }
    return false
}

//This is Hoare partition scheme adapted for this code
func partitionReflect(a ()reflect.Value, low, high int, key string, asc bool) int {
    pivot := a(int((high+low)/2))
    low -= 1
    high += 1

    for {
        if asc {
            low += 1
            for a(low).FieldByName(key) != pivot.FieldByName(key) && aLessThanBReflect(a(low).FieldByName(key), pivot.FieldByName(key)) {
                low += 1
            }
            high -= 1
            for a(high).FieldByName(key) != pivot.FieldByName(key) && !aLessThanBReflect(a(high).FieldByName(key), pivot.FieldByName(key)) {
                high -= 1
            }
        } else {
            low += 1
            for a(low).FieldByName(key) != pivot.FieldByName(key) && !aLessThanBReflect(a(low).FieldByName(key), pivot.FieldByName(key)) {
                low += 1
            }
            high -= 1
            for a(high).FieldByName(key) != pivot.FieldByName(key) && aLessThanBReflect(a(high).FieldByName(key), pivot.FieldByName(key)) {
                high -= 1
            }
        }

        if low >= high {
            return high
        }

        //allocate memory for struct and copy a(low)'s value here
        //couldn't do temp := a(low).Interface() because it shared 1 memory adress
        temp := reflect.New(a(low).Type()).Interface()
        temp = a(low).Interface()
        a(low).Set(a(high))
        a(high).Set(reflect.ValueOf(temp))
    }
}

func sortReflect(a ()reflect.Value, low, high int, key string, asc bool) {
    if low < high {
        p := partitionReflect(a, low, high, key, asc)
        sortReflect(a, low, p, key, asc)
        sortReflect(a, p+1, high, key, asc)
    }
}
```

programming challenge – Improving my solution to the Quiz project from Gophercises in Golang

programming challenge – Improving my solution to the Quiz project from Gophercises in Golang – Code Review Stack Exchange

beginner – Simple RESTful counting API in Golang

Description

This is a simple (and hopefully) RESTful API in Golang that uses Redis. I have next to no prior experience in Golang and absolutely no prior experience with Redis. The API is a simple counter service with the feature of disabling “public” updates (cannot create new counter, and cannot increment existing counter)

The original idea was taken from CountAPI

API reference

  • /api/{key} (namespace defaults to default)
  • /api/{namespace}/{key}

{namespace} and {key} match the regex (a-zA-Z0-9_)+

The following methods are supported on the endpoints:

  • GET: get view count
  • POST: increment view count
  • PATCH: update namespace settings

Example usage

test.py:

from copy import deepcopy
from requests import get, patch, post


def test_expected(method, url, expected, ignored_fields = (), headers = {}, data = {}):
    response = method(url, headers=headers, data=data).json()
    original_response = deepcopy(response)

    print(f'{method.__name__.upper()} {url}')
    print(f'    {response}n')

    for ignored_field in ignored_fields:
        try:
            response.pop(ignored_field)
        except KeyError:
            pass

        try:
            expected.pop(ignored_field)
        except KeyError:
            pass

    assert response == expected, f'got {response}, expected {expected}'

    return original_response


if __name__ == '__main__':
    # Get view count
    test_expected(get, 'http://localhost:8080/api/default/key', {'success': True, 'data': 0})
    test_expected(get, 'http://localhost:8080/api/namespace/key', {'success': True, 'data': 0})
    test_expected(get, 'http://localhost:8080/api/default_namespace_key', {'success': True, 'data': 0})

    # Create new key ("default/key") in namespace that is public (is_public == true)
    test_expected(post, 'http://localhost:8080/api/default/key', {'success': True, 'data': 1, 'isNewNamespace': False})

    # Create new key in non-existent namespace
    response = test_expected(post, 'http://localhost:8080/api/namespace/key', {'success': True, 'data': 1, 'isNewNamespace': True}, ignored_fields=('overrideKey'))
    key = response.get('overrideKey')

    # Check that namespace is not public by default
    test_expected(post, 'http://localhost:8080/api/namespace/key', {'success': False, 'error': 'No permission to update namespace/key'})

    # Check that key in private namespace can be accessed with override key
    test_expected(post, 'http://localhost:8080/api/namespace/key', {'success': True, 'data': 2, 'isNewNamespace': False}, headers={'X-Override-Key': key})

    test_expected(patch, 'http://localhost:8080/api/namespace/key', {'success': False, 'error': 'Incorrect override key'})
    test_expected(patch, 'http://localhost:8080/api/namespace/key', {'success': False, 'error': 'Incorrect override key'}, headers={'X-Override-Key': 'foobar'})
    test_expected(patch, 'http://localhost:8080/api/namespace/key', {'success': False, 'error': 'Field to update does not exist'}, headers={'X-Override-Key': key})
    test_expected(patch, 'http://localhost:8080/api/namespace/key', {'success': True}, headers={'X-Override-Key': key}, data={'field': 'is_public', 'newValue': 'true', 'valueType': 'bool'})

    # Check that everything still works as expected
    test_expected(get, 'http://localhost:8080/api/namespace/key', {'success': True, 'data': 2})
    test_expected(post, 'http://localhost:8080/api/namespace/key', {'success': True, 'data': 3, 'isNewNamespace': False})
    test_expected(patch, 'http://localhost:8080/api/namespace/key', {'success': True}, headers={'X-Override-Key': key}, data={'field': 'is_public', 'newValue': 'false', 'valueType': 'bool'})
    test_expected(get, 'http://localhost:8080/api/namespace/key', {'success': True, 'data': 3})
    test_expected(post, 'http://localhost:8080/api/namespace/key', {'success': False, 'error': 'No permission to update namespace/key'})

    print('All tests passed')

test.py output:

GET http://localhost:8080/api/default/key
    {'success': True, 'data': 0}

GET http://localhost:8080/api/namespace/key
    {'success': True, 'data': 0}

GET http://localhost:8080/api/default_namespace_key
    {'success': True, 'data': 0}

POST http://localhost:8080/api/default/key
    {'success': True, 'data': 1, 'isNewNamespace': False}

POST http://localhost:8080/api/namespace/key
    {'success': True, 'data': 1, 'isNewNamespace': True, 'overrideKey': '+2yubkGY1GwgfIJA'}

POST http://localhost:8080/api/namespace/key
    {'success': False, 'error': 'No permission to update namespace/key'}

POST http://localhost:8080/api/namespace/key
    {'success': True, 'data': 2, 'isNewNamespace': False}

PATCH http://localhost:8080/api/namespace/key
    {'success': False, 'error': 'Incorrect override key'}

PATCH http://localhost:8080/api/namespace/key
    {'success': False, 'error': 'Incorrect override key'}

PATCH http://localhost:8080/api/namespace/key
    {'success': False, 'error': 'Field to update does not exist'}

PATCH http://localhost:8080/api/namespace/key
    {'success': True}

GET http://localhost:8080/api/namespace/key
    {'success': True, 'data': 2}

POST http://localhost:8080/api/namespace/key
    {'success': True, 'data': 3, 'isNewNamespace': False}

PATCH http://localhost:8080/api/namespace/key
    {'success': True}

GET http://localhost:8080/api/namespace/key
    {'success': True, 'data': 3}

POST http://localhost:8080/api/namespace/key
    {'success': False, 'error': 'No permission to update namespace/key'}

All tests passed

Code

WARNING: Redis database running on localhost:6379 may be modified

main.go:

package main

import (
    "encoding/json"
    "log"
    "math/rand"
    "net/http"
    "strconv"
    "strings"
    "time"

    "github.com/gomodule/redigo/redis"
    "github.com/gorilla/mux"
)

var (
    c   redis.Conn
    err error
)

const defaultNamespace = "default"

func apiMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Print(strings.Join(()string{r.RemoteAddr, r.Method, r.URL.Path}, " "))

        w.Header().Set("Content-Type", "application/json")
        next.ServeHTTP(w, r)
    })
}

func mainErrorHandler(w http.ResponseWriter, r *http.Request) {
    const page = `<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Not Found</title>
  </head>
  <body>
    <h1>Not Found</h1>
  </body>
</html>`
    w.Write(()byte(page))
}

func getNamespaceAndKey(m map(string)string) (string, string) {
    namespace := m("namespace")

    if namespace == "" {
        namespace = defaultNamespace
    }

    key := m("key")

    return namespace, key
}

func incrementViewCount(namespace string, key string) int {
    val, err := c.Do("INCR", namespace+":"+key)
    if err != nil {
        log.Fatal(err)
    }

    n, err := redis.Int(val, err)
    if err != nil {
        log.Fatal(err)
    }

    return n
}

func getViewCount(namespace string, key string) int {
    val, err := c.Do("GET", namespace+":"+key)
    if err != nil {
        log.Fatal(err)
    }

    if val == nil {
        return 0
    }

    n, err := redis.Int(val, err)
    if err != nil {
        log.Fatal(err)
    }

    return n
}

func apiErrorHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")

    type Response struct {
        Success bool   `json:"success"`
        Error   string `json:"error"`
    }

    res := Response{Success: false, Error: "Invalid route"}
    json.NewEncoder(w).Encode(res)
}

func apiGetNumViewsHandler(w http.ResponseWriter, r *http.Request) {
    type Response struct {
        Success bool `json:"success"`
        Data    int  `json:"data"`
    }

    vars := mux.Vars(r)

    namespace, key := getNamespaceAndKey(vars)

    n := getViewCount(namespace, key)

    res := Response{Success: true, Data: n}
    json.NewEncoder(w).Encode(res)
}

func namespaceExists(namespace string) bool {
    namespaceExists, err := redis.Bool(c.Do("EXISTS", namespace))
    if err != nil {
        log.Fatal(err)
    }

    return namespaceExists
}

func allowedToUpdateKey(namespace string, key string, userOverrideKey string) bool {
    if !namespaceExists(namespace) {
        return true
    }

    overrideKey, err := redis.String(c.Do("HGET", namespace, "override_key"))
    if err != nil {
        log.Fatal(err)
    }

    if userOverrideKey == overrideKey {
        return true
    }

    namespacePublic, err := redis.Bool(c.Do("HGET", namespace, "is_public"))
    if err != nil {
        log.Fatal(err)
    }

    return namespacePublic
}

var letters = ()rune("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-+")

// From https://stackoverflow.com/a/22892986
func randomOverrideKey(n int) string {
    b := make(()rune, n)
    for i := range b {
        b(i) = letters(rand.Intn(len(letters)))
    }
    return string(b)
}

func createNamespace(namespace string) string {
    overrideKey := randomOverrideKey(16)

    _, err := redis.Int(c.Do("HSET", namespace, "is_public", false, "override_key", overrideKey))
    if err != nil {
        log.Fatal(err)
    }

    return overrideKey
}

func apiPingHandler(w http.ResponseWriter, r *http.Request) {
    type Response struct {
        Success      bool   `json:"success"`
        Data         int    `json:"data"`
        NewNamespace bool   `json:"isNewNamespace"`
        OverrideKey  string `json:"overrideKey,omitempty"`
    }

    type ErrorResponse struct {
        Success bool   `json:"success"`
        Error   string `json:"error"`
    }

    vars := mux.Vars(r)
    namespace, key := getNamespaceAndKey(vars)
    userOverrideKey := r.Header.Get("X-Override-Key")

    if !allowedToUpdateKey(namespace, key, userOverrideKey) {
        res := ErrorResponse{Success: false, Error: "No permission to update " + namespace + "/" + key}
        json.NewEncoder(w).Encode(res)
        return
    }

    isNewNamespace := !namespaceExists(namespace)

    var overrideKey string

    if isNewNamespace {
        overrideKey = createNamespace(namespace)
    } else {
        overrideKey = ""
    }

    n := incrementViewCount(namespace, key)

    res := Response{Success: true, Data: n, NewNamespace: isNewNamespace, OverrideKey: overrideKey}
    json.NewEncoder(w).Encode(res)
}

func convertValueType(value string, valueType string) interface{} {
    var convertedValue interface{}

    // Other types at https://pkg.go.dev/github.com/gomodule/redigo/redis#pkg-index
    // Conversion at https://github.com/gomodule/redigo/blob/72af8129e040d6f962772a8c582e5e9f22085788/redis/reply.go
    switch valueType {
    case "string":
        convertedValue, err = redis.String(value, err)
        if err != nil {
            log.Fatalf(`Could not convert "%v" to type "%v" (invalid value): %v`, value, valueType, err)
        }
    case "bool":
        convertedValue, err = redis.Bool(()byte(value), err)
        if err != nil {
            log.Fatalf(`Could not convert "%v" to type "%v" (invalid value): %v`, value, valueType, err)
        }
    case "int":
        convertedValue, err = redis.Int(()byte(value), err)
        if err != nil {
            log.Fatalf(`Could not convert "%v" to type "%v" (invalid value): %v`, value, valueType, err)
        }
    default:
        log.Fatalf(`Could not convert "%v" to type "%v" (unknown type)`, value, valueType)
    }

    return convertedValue
}

func apiUpdateHandler(w http.ResponseWriter, r *http.Request) {
    userOverrideKey := r.Header.Get("X-Override-Key")

    vars := mux.Vars(r)
    namespace, _ := getNamespaceAndKey(vars)

    overrideKey, err := redis.String(c.Do("HGET", namespace, "override_key"))
    if err != nil {
        log.Fatal(err)
    }

    type Response struct {
        Success bool `json:"success"`
    }

    type ErrorResponse struct {
        Success bool   `json:"success"`
        Error   string `json:"error"`
    }

    if userOverrideKey != overrideKey {
        res := ErrorResponse{Success: false, Error: "Incorrect override key"}
        json.NewEncoder(w).Encode(res)
        return
    }

    field := r.FormValue("field")
    value := r.FormValue("newValue")
    valueType := r.FormValue("valueType")

    exists, err := redis.Int(c.Do("HEXISTS", namespace, field))
    if err != nil {
        log.Fatal(err)
    }

    if exists == 0 {
        res := ErrorResponse{Success: false, Error: "Field to update does not exist"}
        json.NewEncoder(w).Encode(res)
        return
    }

    convertedValue := convertValueType(value, valueType)

    _, err = redis.Int(c.Do("HSET", namespace, field, convertedValue))
    if err != nil {
        log.Fatal(err)
    }

    res := Response{Success: true}
    json.NewEncoder(w).Encode(res)
}

func init() {
    c, err = redis.Dial("tcp", ":6379")
    if err != nil {
        log.Fatal(err)
    }
    log.Print("Connected to database")

    s := time.Now().UnixNano()
    rand.Seed(s)
    log.Print("Seeded with " + strings.ToUpper(strconv.FormatInt(s, 16)))

    createNamespace(defaultNamespace)
    _, err = c.Do("HSET", defaultNamespace, "is_public", convertValueType("true", "bool"))
    if err != nil {
        log.Fatal(err)
    }
}

func main() {
    defer c.Close()

    r := mux.NewRouter()
    r.NotFoundHandler = http.HandlerFunc(mainErrorHandler)

    s := r.PathPrefix("/api/").Subrouter()
    s.Use(apiMiddleware)

    allowedCharsRegex := "(a-zA-Z0-9_)+"

    s.Path("/{key:" + allowedCharsRegex + "}").HandlerFunc(apiGetNumViewsHandler).Methods("GET")
    s.Path("/{namespace:" + allowedCharsRegex + "}/{key:" + allowedCharsRegex + "}").HandlerFunc(apiGetNumViewsHandler).Methods("GET")

    s.Path("/{key:" + allowedCharsRegex + "}").HandlerFunc(apiPingHandler).Methods("POST")
    s.Path("/{namespace:" + allowedCharsRegex + "}/{key:" + allowedCharsRegex + "}").HandlerFunc(apiPingHandler).Methods("POST")

    s.Path("/{key:" + allowedCharsRegex + "}").HandlerFunc(apiUpdateHandler).Methods("PATCH")
    s.Path("/{namespace:" + allowedCharsRegex + "}/{key:" + allowedCharsRegex + "}").HandlerFunc(apiUpdateHandler).Methods("PATCH")

    s.NotFoundHandler = http.HandlerFunc(apiErrorHandler)
    log.Print("Routes set up successfully")

    http.ListenAndServe(":8080", r)
}

go.mod:

module hit-counter

go 1.16

require (
    github.com/gomodule/redigo v1.8.4
    github.com/gorilla/mux v1.8.0
)

go.sum:

github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gomodule/redigo v1.8.4 h1:Z5JUg94HMTR1XpwBaSH4vq3+PNSIykBLxMdglbw10gg=
github.com/gomodule/redigo v1.8.4/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
github.com/gomodule/redigo/redis v0.0.0-do-not-use h1:J7XIp6Kau0WoyT4JtXHT3Ei0gA1KkSc6bc87j9v9WIo=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

DreamProxies - Cheapest USA Elite Private Proxies 100 Cheapest USA Private Proxies Buy 200 Cheap USA Private Proxies 400 Best Private Proxies Cheap 1000 USA Private Proxies 2000 USA Private Proxies 5000 Cheap USA Private Proxies ExtraProxies.com - Buy Cheap Private Proxies Buy 50 Private Proxies Buy 100 Private Proxies Buy 200 Private Proxies Buy 500 Private Proxies Buy 1000 Private Proxies Buy 2000 Private Proxies ProxiesLive.com Proxies-free.com New Proxy Lists Every Day Proxies123.com Proxyti.com Buy Quality Private Proxies