I’m working on a library to help get the “real” client IP from HTTP requests:
https://github.com/realclientip/realclientip-go
https://pkg.go.dev/github.com/realclientip/realclientip-go
Right now the “strategies” are like:
type Strategy func(headers http.Header, remoteAddr string) string
...
clientIPStrategy, err := realclientip.RightmostTrustedCountStrategy("X-Forwarded-For", 1)
...
clientIP := clientIPStrategy(req.Header, req.RemoteAddr)
So, functions matching a signature are created that process the input.
But I keep wondering: Should I be returning types (structs) adhering to an interface instead?
I’m starting to think I should, but I can’t think of what difference it would make.
Any feedback would be appreciated.
Adam Pritchard
Any feedback would be appreciated.
Adam Pritchard
--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/20a8eac9-98b2-4af5-87f3-e811d5a97e3cn%40googlegroups.com.
Thank you both for your responses.
I’m going to switch to returning structs that implement an unexported interface. I don’t have a super convincing justification, but here are the reasons…
1: It’s unexported because it’s not expected to be implemented externally and passed in. But if a caller wants to implement their own (and still use Must or ChainStategies, for example), then they can define the interface themselves — I’ll have comment indicating this. (This unexported interface thing is used by pkg/errors and I’ve read reference to it elsewhere, but I can’t find where now.)
2: Why I’m concerned with implementing a particular interface or function signature at all, when I could just return any old thing: I would like to think that some users of the library would make the strategy a runtime decision based on configuration. So it would be nice if their code paths don’t care which strategy it is.
3: If the caller wants just a function, they can easily get with a bound method. Something like:
getClientIP := realclientip.NewSingleIPHeaderStrategy("X-Real-IP").ClientIP
Not lovely, but it works the same.
4: As Martin said, an interface means I could “potentially add other methods to it in future without breaking compatibility”. Now, I really, specifically want this to be very simple, slim library (I really do want it to be reimplemented in other languages), but I can imagine, say, collecting stats on calls and failures (empty string returns). Then adding a String() method that prints those stats and/or the strategy type and configuration (I like logging such things). (Yes, the library user could also just add a wrapper that does that.)
I haven’t start making the changes yet, so it could all feel like garbage — we’ll see. I’m also not yet settled on the names of things — I’ll pay more attention to the verb-iness/noun-iness.
Thanks again.
Adam