[Go language] Be careful when creating a server with mux + cors + alice. Especially about what I was addicted to around CORS.

Introduction

gorilla / mux, rs / cors, [justinas / alice](https://github. I will show you how to make a server by combining com / justinas / alice). In particular, when I tried to divide the middleware to be applied according to the path, I was addicted to CORS, so I will focus on the part related to that. In the code to be posted, it is basically a way of writing that ignores errors, so please rewrite as appropriate. Also, note that ... in the code simply means abbreviation and has no grammatical meaning, so be careful when copying and pasting.

tl;dr

Watch out for the ʻOPTIONS` method of the preflight request.

A brief overview of mux, cors and alice

Some examples are shown, but since it is the basic content as described in each README, if you know it, skip to [Implementation main subject](#Implementation main subject).

gorilla/mux

Here is an example of a server using mux.

package main

import (
  "encoding/json"
  "net/http"

  "github.com/gorilla/mux"
)

type Character struct {
  Name string `json:"name"`
}

func pilotFunc(w http.ResponseWriter, r *http.Request) {
  res, _ := json.Marshal(Character{"Shinji"})
  w.WriteHeader(http.StatusOK)
  w.Write(res)
}

func angelFunc(w http.ResponseWriter, r *http.Request) {
  res, _ := json.Marshal(Character{"Adam"})
  w.WriteHeader(http.StatusOK)
  w.Write(res)
}

func main() {
  r := mux.NewRouter()  //r is*mux.Router type
  r.Methods("GET").Path("/pilot").HandlerFunc(pilotFunc)  // r.to routes*mux.Add a Route.
  r.Methods("GET").Path("/angel").HandlerFunc(angelFunc)  // r.routes is[]*mux.Since it is a Route type, you can add more and more.

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

With this, a little server is completed.

$ go run main.go

$ curl http://localhost:8000/pilot
{"name": "Shinji"}

However, when I use fetch from a browser, I get the following error:

Access to fetch at 'http://localhost:8000/user' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

rs/cors

That's where rs / cors comes in. For CORS, the MDN page (https://developer.mozilla.org/ja/docs/Web/HTTP/CORS) is easy to understand. In order to support CORS, it is necessary to set an appropriate value in the response header on the server side. Rs / cors is responsible for that. This is the previous example with rs / cors added.

import (
  ...
  "github.com/rs/cors"
)

func main() {
  r := mux.NewRouter()
  r.Methods("GET").Path("/pilot").HandlerFunc(pilotFunc)
  r.Methods("GET").Path("/angel").HandlerFunc(angelFunc)
  c := cors.Default().Handler(r)  //Added.

  http.ListenAndServe(":8000", c)  //Changed from r to c.
}

You can support CORS just by adding one line.

justinas/alice

Furthermore, justinas / alice is useful when you want to add middleware that outputs the contents of the request to the log each time, or middleware that acquires the value from the request header before performing the main processing. (: //github.com/justinas/alice). Here is an example of creating and adding middleware that outputs request information to a log.

import (
  ...
  "log"

  "github.com/justinas/alice"
)

func logHandler(h http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    log.Printf("Method: %v; URL: %v; Protocol: %v", r.Method, r.URL, r.Proto)
    h.ServeHTTP(w, r)
  })
}

func main() {
  r := mux.NewRouter()
  r.Methods("GET").Path("/pilot").HandlerFunc(pilotFunc)
  r.Methods("GET").Path("/angel").HandlerFunc(angelFunc)
  c := cors.Default()
  chain := alice.New(c.Handler, logHandler).Then(r)  //Added.

  http.ListenAndServe(":8000", chain)  //Changed from c to chain.
}

Implementation subject

By the way, gorilla / mux, rs / cors, [justinas / alice](https: // I will repost the combination of all combinations (github.com/justinas/alice) (definition of packge, import, struct is omitted).

...

func pilotFunc(w http.ResponseWriter, r *http.Request) {
  res, _ := json.Marshal(Character{"Shinji"})
  w.WriteHeader(http.StatusOK)
  w.Write(res)
}

func angelFunc(w http.ResponseWriter, r *http.Request) {
  res, _ := json.Marshal(Character{"Adam"})
  w.WriteHeader(http.StatusOK)
  w.Write(res)
}

func logHandler(h http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    log.Printf("Method: %v; URL: %v; Protocol: %v", r.Method, r.URL, r.Proto)
    h.ServeHTTP(w, r)
  })
}

func main() {
  r := mux.NewRouter()
  r.Methods("GET").Path("/pilot").HandlerFunc(pilotFunc)
  r.Methods("GET").Path("/angel").HandlerFunc(angelFunc)
  c := cors.Default()

  chain := alice.New(c.Handler, logHandler).Then(r)

  http.ListenAndServe(":8000", chain)
}

However, with this method, the set middleware will be applied to all requests.

For example, / pilot checks if you are logged in and returns data only if you are logged in, but / angel cannot respond to requests that return data even if you are not logged in. With Qiita, you can see the article page even if you are not logged in, but imagine a situation where you cannot see the draft of My Page unless you are logged in.

Therefore, try the following. As I said earlier, I'm addicted to the writing style below (laughs).

type funcHandler struct {
  handler func(w http.ResponseWriter, r *http.Request)
}

func (h funcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  h.handler(w, r)
}

func pilotFunc(w http.ResponseWriter, r *http.Request) {...}
func angelFunc(w http.ResponseWriter, r *http.Request) {...}
func logHandler(h http.Handler) http.Handler {...}

func authHandler(h http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    log.Println("Authentication")
    h.ServeHTTP(w, r)
  })
}

func main() {
  c := cors.Default()

  logChain := alice.New(c.Handler, logHandler)
  authChain := logChain.Append(authHandler)

  r := mux.NewRouter()
  r.Methods("GET").Path("/pilot").Handler(authChain.Then(funcHandler{pilotFunc}))
  r.Methods("GET").Path("/angel").Handler(logChain.Then(funcHandler{angelFunc}))

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

Now, only when fetch is done to / pilot, ʻAuthentication` will be output to the standard output. Now it is possible to separate the middleware to be applied according to the path.

$go run main.go
2020/09/29 20:41:18 Method: GET; URL: /pilot; Protocol: HTTP/1.1
2020/09/29 20:41:18 Method: GET; URL: /pilot; Protocol: HTTP/1.1; Authentication
2020/09/29 20:41:18 Method: GET; URL: /angel; Protocol: HTTP/1.1

I'll leave an explanation of why this is addictive, and briefly explain the mechanism by which it can be rewritten in this way. If you want to see the reason why you are addicted first, please skip to [Reason for addiction](# Reason for addiction).

Reason for rewriting (supplement)

First of all, I would like to confirm what http.ListenAndServer is doing, but [Go] How to read the net / http package and execute http.HandleFunc / items / 1d1c64d05f7e72e31a98) is quite detailed, so I will throw it here. It seems that the information is a little old, and the line number written does not match the actual line number, or the writing style is slightly different, but I think that there is no problem in basic understanding.

In conclusion, http.ListenAndServe passes the received request to ServeHTTP in the second argument (this time mux.Router). Therefore, the second argument must implement ServeHTTP. ServeHTTP is implemented in mux.Router, and in the http package,

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

Any http.Handler type variable can be the second argument to ListenAndServe. Within this ServeHTTP, call the ServeHTTP of the other http.Hanlder, and then within that ServeHTTP, call the ServeHTTP of the other http.Hanlder, and so on. By chaining .Handler`, you can process requests in order.

In other words, you can chain handlers by using a function that takes http.Handler as an argument and returns a http.Handler type, ** or middleware **. As an example, consider the chain of logHandler and ʻauthHandler`.

chain := logHandler(authHandler(router))

Such a chain can be considered. However, with this method, it becomes more difficult to understand as the number of middleware increases, so using alice,

chain := alice.New(logHandler, authHandler).Then(router)

Is written briefly.

Here, let's compare before and after rewriting so that the middleware applied can be changed depending on the path.

//Change before
func main() {
  r := mux.NewRouter()
  r.Methods("GET").Path("/pilot").HandlerFunc(pilotFunc)
  r.Methods("GET").Path("/angel").HandlerFunc(angelFunc)
  c := cors.Default()
  chain := alice.New(c.Handler, logHandler).Then(r)
  http.ListenAndServe(":8000", chain)
}

//After change
func main() {
  c := cors.Default()
  logChain := alice.New(c.Handler, logHandler)
  authChain := logChain.Append(authHandler)
  r := mux.NewRouter()
  r.Methods("GET").Path("/pilot").Handler(authChain.Then(funcHandler{pilotFunc}))
  r.Methods("GET").Path("/angel").Handler(logChain.Then(funcHandler{angelFunc}))
  http.ListenAndServe(":8000", r)
}

Therefore, the flow that the request before and after the change follows is

Previous: request -> CORS    -> logging -> routing           -> pilotFunc or angelFunc
rear: request -> routing -> CORS    -> logging (-> auth) -> pilotFunc or angelFunc 

You can see that the routing order has changed. This is one of the reasons why I am addicted to it.

Next, I'll explain why I'm addicted to CORS, which is the main subject of this article.

Reasons to get hooked

First, I will describe an implementation of the authentication mechanism (details will be described later) that is a little closer to the actual one.

func authHandler(h http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    token := r.Header.Get("Token")
    if token == "" {
      w.WriteHeader(http.StatusUnauthorized)
      return
    }
    h.ServeHTTP(w, r)
  })
}
entire code
package main

import (
  "encoding/json"
  "log"
  "net/http"

  "github.com/gorilla/mux"
  "github.com/justinas/alice"
  "github.com/rs/cors"
)

type Character struct {
  Name string `json:"name"`
}

type funcHandler struct {
  handler func(w http.ResponseWriter, r *http.Request)
}

func (h funcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  h.handler(w, r)
}

func pilotFunc(w http.ResponseWriter, r *http.Request) {
  res, _ := json.Marshal(Character{"Shinji"})
  w.WriteHeader(http.StatusOK)
  w.Write(res)
}

func angelFunc(w http.ResponseWriter, r *http.Request) {
  res, _ := json.Marshal(Character{"Adam"})
  w.WriteHeader(http.StatusOK)
  w.Write(res)
}

func logHandler(h http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    log.Printf("Method: %v; URL: %v; Protocol: %v", r.Method, r.URL, r.Proto)
    h.ServeHTTP(w, r)
  })
}

func authHandler(h http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    token := r.Header.Get("Token")
    if token == "" {
      w.WriteHeader(http.StatusUnauthorized)
      return
    }
    h.ServeHTTP(w, r)
  })
}

func main() {
  c := cors.Default()
  logChain := alice.New(c.Handler, logHandler)
  authChain := logChain.Append(authHandler)
  r := mux.NewRouter()
  r.Methods("GET").Path("/pilot").Handler(authChain.Then(funcHandler{pilotFunc}))
  r.Methods("GET").Path("/angel").Handler(logChain.Then(funcHandler{angelFunc}))
  http.ListenAndServe(":8000", r)
}

In this state

fetch(url, {
  headers: new Headers({
    Token: "abcd"
  })
})

When you execute ...

Access to fetch at 'http://localhost:8000/pilot' from origin 'null' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

** An error will occur. ** **

To understand why you're addicted

  1. Authentication mechanism
  2. Preflight request
  3. Request processing order

It is necessary to understand the three points of. I will explain in order.

1. Authentication mechanism

When authenticating, whether using cookies or Tokens, it is necessary to put it in the request header and pass it from the front end to the back end. Therefore, if you reimplement ʻauthHandler` used in the example to something more realistic,

func authHandler(h http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    token := r.Header.Get("Token")
    if token == "" {
    ...
    }
    ...
    h.ServeHTTP(w, r)
  })
}

It will be something like that. The important thing here is that you need to add a new ** header name ** that identifies it as a cookie or Token.

2. Preflight request

Actually, CORS has a limitation, and you can make a "simple request" only when the values that can be set in the method and header meet the following conditions. Here, a simple request is a general request.

method : GET, POST, HEAD header : Accept, Accept-Language, Content-Language, Content-Type(application/x-www-form-urlencoded, multipart/form-data, text/plain), DPR, Downlink, Save-Data, Viewport-Width, Width

Therefore, if you set your own values such as Cookie or Token in the ** header, you cannot make a simple request **. In this case, make a request called ** Preflight Request ** in advance. See MDN for more information. If you write only the necessary information, the request will be sent as a preflight request with the ʻOPTIONSmethod. Only if a normal response is returned to this preflight request, we will continue to send aGET or POSTrequest with a value such asTokenin the header. Therefore, when usingfetch`, the request is sent twice behind the scenes.

3. Request processing order

In summary, if you use your own Header value, a preflight request will be sent. Here, I will repost the flow before and after changing so that multiple middleware can be applied.

Previous: request -> CORS    -> logging -> routing           -> pilotFunc or angelFunc
rear: request -> routing -> CORS    -> logging (-> auth) -> pilotFunc or angelFunc 

Looking at the CORS middleware Handler,

func (c *Cors) Handler(h http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" {
      ...
      w.WriteHeader(http.StatusNoContent)
    } else {
      ...
      h.ServeHTTP(w, r)
    }
  })
}

So, the process is branched depending on whether it is the Options method or not, and the preflight request is handled appropriately. Next, take a look at ServeHTTP of mux.Router.

func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
  ...
  if r.Match(req, &match) {
    handler = match.Handler
    ...
  }

  if handler == nil && match.MatchErr == ErrMethodMismatch {
    handler = methodNotAllowedHandler()
  }

  if handler == nil {
    handler = http.NotFoundHandler()
  }

  handler.ServeHTTP(w, req)
}

It looks for a handler that matches the value of ʻurl` in the request, and if it doesn't, it branches between the method with an error and the routing with an error to handle the error. ..

Therefore, before and after changing to be able to apply multiple middleware, the processing for preflight requests has changed as follows.

Previous: preflight request -> CORS check    -> 204 No content
rear: preflight request -> routing check -> 405 Method Not Allowed

This is why we are getting CORS errors.

Error resolution method

Once you know this, it's easy to deal with. For example, is it simplest to add ʻOPTINOSwhen routingmux.Router`? Sorry for the later, but in rs / cors, when adding your own header, you need to specify what kind of header is allowed. See the README for details. Based on that, the implementation will be as follows.

func main() {
  c := cors.New(cors.Options{
    AllowedOrigins: []string{"*"},
    AllowedMethods: []string{
      http.MethodHead,
      http.MethodGet,
      http.MethodPost,
    },
    AllowedHeaders:   []string{"*"},
    AllowCredentials: false,
  })
  logChain := alice.New(c.Handler, logHandler)
  authChain := logChain.Append(authHandler)
  r := mux.NewRouter()
  r.Methods("GET", "OPTIONS").Path("/pilot").Handler(authChain.Then(funcHandler{pilotFunc}))
  r.Methods("GET", "OPTIONS").Path("/angel").Handler(logChain.Then(funcHandler{angelFunc}))
  http.ListenAndServe(":8000", r)
}
Overall code
package main

import (
  "encoding/json"
  "log"
  "net/http"

  "github.com/gorilla/mux"
  "github.com/justinas/alice"
  "github.com/rs/cors"
)

type Character struct {
  Name string `json:"name"`
}

type funcHandler struct {
  handler func(w http.ResponseWriter, r *http.Request)
}

func (h funcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  h.handler(w, r)
}

func pilotFunc(w http.ResponseWriter, r *http.Request) {
  res, _ := json.Marshal(Character{"Shinji"})
  w.WriteHeader(http.StatusOK)
  w.Write(res)
}

func angelFunc(w http.ResponseWriter, r *http.Request) {
  res, _ := json.Marshal(Character{"Adam"})
  w.WriteHeader(http.StatusOK)
  w.Write(res)
}

func logHandler(h http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    log.Printf("Method: %v; URL: %v; Protocol: %v", r.Method, r.URL, r.Proto)
    h.ServeHTTP(w, r)
  })
}

func authHandler(h http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    token := r.Header.Get("Token")
    if token == "" {
      w.WriteHeader(http.StatusUnauthorized)
      return
    }
    h.ServeHTTP(w, r)
  })
}

func main() {
  c := cors.New(cors.Options{
    AllowedOrigins: []string{"*"},
    AllowedMethods: []string{
      http.MethodHead,
      http.MethodGet,
      http.MethodPost,
    },
    AllowedHeaders:   []string{"*"},
    AllowCredentials: false,
  })
  logChain := alice.New(c.Handler, logHandler)
  authChain := logChain.Append(authHandler)
  r := mux.NewRouter()
  r.Methods("GET", "OPTIONS").Path("/pilot").Handler(authChain.Then(funcHandler{pilotFunc}))
  r.Methods("GET", "OPTIONS").Path("/angel").Handler(logChain.Then(funcHandler{angelFunc}))
  http.ListenAndServe(":8000", r)
}

Alternatively, you can use PathPrefix to handle all ʻOPTIONS`. (I don't know if this method is good, I'm sorry.)

func main() {
  c := cors.New(cors.Options{
    AllowedOrigins: []string{"*"},
    AllowedMethods: []string{
      http.MethodHead,
      http.MethodGet,
      http.MethodPost,
    },
    AllowedHeaders:   []string{"*"},
    AllowCredentials: false,
  })
  logChain := alice.New(c.Handler, logHandler)
  authChain := logChain.Append(authHandler)
  r := mux.NewRouter()
  r.Methods("OPTIONS").PathPrefix("/").HandlerFunc(c.HandlerFunc)
  r.Methods("GET").Path("/pilot").Handler(authChain.Then(funcHandler{pilotFunc}))
  r.Methods("GET").Path("/angel").Handler(logChain.Then(funcHandler{angelFunc}))
  http.ListenAndServe(":8000", r)
}

By the way, as the name suggests, Pathprefix adds a common prefix to urls. For example

r.PathPrefix("/products/")

If set to, requests such as http: // localhost: 8000 / products / hoge and http: // localhost: 8000 / products / fuga will be applicable.

Conclusion

If you want to support CORS, make sure to support ʻOPTIONS`.

Digression

Thank you for reading the long text. When you understand it, it seems like it's a simple thing, but there is one more thing to do. Actually, I didn't write it from scratch, but changed what other people wrote, and I got hooked in the process. The code written by other people is

func main() {
  c := cors.New(cors.Options{
    AllowedOrigins: []string{"*"},
    AllowedMethods: []string{
      http.MethodHead,
      http.MethodGet,
      http.MethodPost,
    },
    AllowedHeaders:   []string{"*"},
    AllowCredentials: false,
  })
  logChain := alice.New(c.Handler, logHandler)
  authChain := logChain.Append(authHandler)
  r := mux.NewRouter()
  r.Methods("GET").Path("/pilot").Handler(authChain.Then(funcHandler{pilotFunc}))
  r.Methods("GET").Path("/angel").Handler(logChain.Then(funcHandler{angelFunc}))
  
  r.PathPrefix("").Handler(logChain.Then(http.StripPrefix("/img", http.FileServer(http.Dir("./img")))))  //There was this.
  
  http.ListenAndServe(":8000", r)
}

It was like that. To return the image, it is called r.PathPrefix (""). Handler (logChain.Then (http.StripPrefix ("/ img", http.FileServer (http.Dir ("./ img"))))) There was something. However, I didn't have a chance to return the image on my server, so I deleted this line. Then, suddenly an error started to occur in CORS and it was panic. In addition, moving this line to the beginning resulted in an error. However, it was difficult to identify the cause of the error.

I knew at the time of writing this article

CORS can fail with various errors, but for security reasons it is stipulated that the errors cannot be known from JavaScript. The code only tells you that an error has occurred. The only way to know exactly what went wrong is to look at the details in your browser console.

... apparently ... In other words, no matter what error occurs

Access to fetch at 'http://localhost:8000/pilot' from origin 'null' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

Is displayed. This made debugging difficult. When I think about it now, I can't deny the feeling that I wasted my time thinking that I could guess what kind of error could occur by looking at the types of 4XX errors from the Network tab in chrome. is. In fact, when I removed the Pathprefix ("") and when I brought it to the beginning, the error text displayed on the console was the same, but the status was 405 and 404, respectively.

So why is r.PathPrefix (""). Handler (logChain.Then (http.StripPrefix ("/ img", http.FileServer (http.Dir ("./ img"))))) added If so, did the error occur? The request ʻOPTIONS http: // localhost: 8000 / pilot`

r.Methods("GET").Path("/pilot").Handler(authChain.Then(funcHandler{pilotFunc}))

I get a Method Not Allowed error for

r.PathPrefix("").Handler(logChain.Then(http.StripPrefix("/img", http.FileServer(http.Dir("./img")))))

Will fit against. Actually, the path registered with gorilla / mux is converted to a regular expression, but if you register an empty string "" in PathPrefix, the corresponding regular expression will be"^". Therefore, / pilot will not be Not Found for the above routing and will proceed.

Also, if the method is ʻOPTIONS, rs / cors immediately returns 204 No Content, so the subsequent http.StripPrefix ("/ img", http.FileServer (http.Dir ("./ img")) Requests that cause an error with))) `will also be passed. As a result, the routing you set up to return the image unintentionally avoided CORS errors.

Finally

There is an idea that the Go language is a combination of simple ones, but for gollira / mux, rs / cors, and justinas / alice this time, the number of files to read to understand the behavior was one or two. , It was easy to read the process in order. I don't think the explanations in this article have completely explained everything, so if you have any questions, please take a look at each implementation for yourself.

Recommended Posts

[Go language] Be careful when creating a server with mux + cors + alice. Especially about what I was addicted to around CORS.
A note I was addicted to when creating a table with SQLAlchemy
What I was addicted to when creating a web application in a windows environment
What I was careful about when implementing Airflow with docker-compose
What I was addicted to when dealing with huge files in a Linux 32bit environment
I was addicted to creating a Python venv environment with VS Code
A note I was addicted to when running Python with Visual Studio Code
A story that I was addicted to when I made SFTP communication with python
What I was addicted to when using Python tornado
What I was addicted to when migrating Processing users to Python
What I was worried about when displaying images with matplotlib
The story I was addicted to when I specified nil as a function argument in Go
What I was addicted to when introducing ALE to Vim for Python
What I was addicted to with json.dumps in Python base64 encoding
A note I was addicted to when making a beep on Linux
[Python] When I tried to make a decompression tool with a zip file I just knew, I was addicted to sys.exit ()
A story I was addicted to when inserting from Python to a PostgreSQL table
A story I was addicted to trying to get a video url with tweepy
I was addicted to trying Cython with PyCharm, so make a note
What I was addicted to Python autorun
Three things I was addicted to when using Python and MySQL with Docker
When I connect to a remote Jupyter Server with VScode, it's remote but local
When I tried to connect with SSH, I got a warning about free space.
[Ansible] What I am careful about when writing ansible
Pretend to be a server with two PCs
What I was addicted to when combining class inheritance and Joint Table Inheritance in SQLAlchemy
What I did when I was angry to put it in with the enable-shared option
I wrote a CLI tool in Go language to view Qiita's tag feed with CLI
It was a painful memory when I was told TypeError: must be type, not class obj when trying to inherit with Python.
I was addicted to scraping with Selenium (+ Python) in 2020
A story that I was addicted to at np.where
When I tried to install PIL and matplotlib in a virtualenv environment, I was addicted to it.
When creating a pipenv environment, I got addicted to "Value Error: Not a valid python path"
What I was addicted to when I built my own neural network using the weights and biases I got with scikit-learn's MLP Classifier.
What I was addicted to in Collective Intelligence Chaprter 3. It's not a typo, so I think something is wrong with my code.