Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Customizable net.Listener for advanced use cases, such as tunneling #245

Open
wants to merge 34 commits into
base: main
Choose a base branch
from

Conversation

Ignaciojeria
Copy link

image

@ccoVeille
Copy link
Collaborator

No test added?

@dylanhitt
Copy link
Collaborator

Would you mind following the With* pattern as well such as here

@dylanhitt
Copy link
Collaborator

dylanhitt commented Nov 30, 2024

Also we have two entrypoints Run and RunTLS. Updating both would be best.

Also maybe a refactor is in order within the s.setup method. We should probably create the default listener in the Server constructor, and instead of calling ListenAndServe* we just call Serve*.

Copy link
Member

@EwenQuim EwenQuim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot, good idea!
If you can make the requested changes, it would be nice.

@dylanhitt
Copy link
Collaborator

Pardon me. I was on a phone so I was short.

I will say this is a great contribution. I hope you’re enjoying fuego. 😄

@Ignaciojeria
Copy link
Author

I've addressed some of the suggestions and pushed a new change. I haven't had the chance to fully test it yet, but I'll be running some tests soon. Any feedback is greatly appreciated!

serve.go Outdated
Comment on lines 20 to 23
if err := s.setupDefaultListener(); err != nil {
return fmt.Errorf("failed to start server: %w", err)
}
s.setup()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The setup listener is now called before setup, at all place setup is called if I'm not wrong

What about calling it in setup, and update setup signature to return an error?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! Two heads are better than one. I made some adjustments and have been running manual tests (locally only).

serve.go Outdated
Comment on lines 32 to 31
if err := s.setup(certFile, keyFile); err != nil {
return err
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't you call WithTLSListener here?

It would allow to pass certFile and keyFile to setup, and the check on isTLS

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code was significantly simplified based on the observation. Can you help check if there’s any room for improvement? Thank you very much!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great.

I'll open a PR to your fork

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I opened Ignaciojeria#1

serve.go Outdated
@@ -100,7 +100,7 @@ func (s *Server) setupDefaultListener() error {
}
addr := s.Server.Addr
if addr == "" {
addr = "127.0.0.1:9999"
addr = ":9999"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please check TestPetstoreOpenAPIGeneration

it seems broken now

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i will check now

Copy link
Author

@Ignaciojeria Ignaciojeria Dec 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any problem, but I can add host:port by default to avoid breaking tests. I will run the tests in the next iteration. Let me handle the issues. I'll make sure to run the tests and update you on how it goes.

image

image

image

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did it in Ignaciojeria#1

But let me ask you the question here.

Why did you remove the local host? To listen on all interfaces?

Was this changes needed in this exact PR?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I maintained localhost as the default to preserve the original implementation

@ccoVeille
Copy link
Collaborator

I'm not a fuego maintainer

I opened
Ignaciojeria#1

Please consider it.

server.go Outdated Show resolved Hide resolved
server.go Outdated
Comment on lines 165 to 159
func getServerAddress(s *Server) string {
if s.Listener != nil {
return s.Listener.Addr().String()
}
if s.Server.Addr != "" {
return s.Server.Addr
}
// Default address if none is set
return ":9999"
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no reason for this method.

setListener now sets s.Server.Addr

Copy link
Author

@Ignaciojeria Ignaciojeria Dec 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

function removed

server.go Outdated
@@ -68,6 +70,8 @@ type Server struct {

OpenApiSpec openapi3.T // OpenAPI spec generated by the server

Listener net.Listener
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are adding a listener

So it should be closed when server stops

So please add this

func (s *Server) Close () {
	if s.Listener != nil }
		s.Listener.Close()
	}
	s.Server.Close()
	return
}

Copy link
Author

@Ignaciojeria Ignaciojeria Dec 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

method added

server.go Outdated
@@ -68,6 +70,8 @@ type Server struct {

OpenApiSpec openapi3.T // OpenAPI spec generated by the server

Listener net.Listener
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a need for the listener to be public, you added WithListener

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are right now is private

server.go Outdated
@@ -125,7 +129,6 @@ func NewServer(options ...func(*Server)) *Server {
}

defaultOptions := [...]func(*Server){
WithAddr("localhost:9999"),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a problem with this.

Please take a look at the PR I opened in your fork

Copy link
Author

@Ignaciojeria Ignaciojeria Dec 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR merged. I removed WithTLSListener because it added unnecessary complexity. I believe RunTLS and WithListener already provide the user with the necessary customization options. Please correct me if I’m wrong.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought the same. 👍

Also, semantically, I was annoyed that WithTLSListener was not allowing to pass a listener

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused on why this is a problem?

server.go Outdated
Comment on lines 366 to 367
// WithListener configures the server to use a custom listener.
// If no listener is provided, it creates a default listener using the server's address.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// WithListener configures the server to use a custom listener.
// If no listener is provided, it creates a default listener using the server's address.
// WithListener configures the server to use a custom listener.

The sentence is no longer valid, as it raises a panic

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sentence removed

server.go Outdated
Comment on lines 368 to 384
func WithListener(listener net.Listener) func(*Server) {
return func(s *Server) {
if s.listener != nil {
panic("a listener is already configured; cannot overwrite it")
}
s.listener = listener
s.Server.Addr = listener.Addr().String()
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check, but I think there is a need for something like this

Suggested change
func WithListener(listener net.Listener) func(*Server) {
return func(s *Server) {
if s.listener != nil {
panic("a listener is already configured; cannot overwrite it")
}
s.listener = listener
s.Server.Addr = listener.Addr().String()
}
}
func WithListener(listener net.Listener) func(*Server) {
return func(s *Server) {
if s.listener != nil {
panic("a listener is already configured; cannot overwrite it")
}
s.listener = listener
if _, ok := listener.(tls.Listener) {
t.isTLS = true
}
s.Server.Addr = listener.Addr().String()
}
}

This is pseudo code made on my phone, you may need to adapt/fix it

Copy link
Author

@Ignaciojeria Ignaciojeria Dec 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used reflection to check if the instance is of type tls.listener

// WithListener configures the server to use a custom listener.
func WithListener(listener net.Listener) func(*Server) {
	return func(s *Server) {
		if s.listener != nil {
			panic("a listener is already configured; cannot overwrite it")
		}
		s.isTLS = isTLSListener(listener)
		WithAddr(listener.Addr().String())(s)
		s.listener = listener
	}
}

func isTLSListener(listener net.Listener) bool {
	listenerType := reflect.TypeOf(listener)
	if listenerType != nil && listenerType.String() == "*tls.listener" {
		return true
	}
	return false
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code is way better, but using assertion seems overkill, also it's very slow.

server.go Outdated
s.OpenApiSpec.Servers = append(s.OpenApiSpec.Servers, &openapi3.Server{
URL: "http://" + s.Addr,
URL: "http://" + s.Server.Addr,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it be https:// when isTLS is true?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, i get protocol from proto() method

server.go Outdated
Comment on lines 379 to 385
func isTLSListener(listener net.Listener) bool {
listenerType := reflect.TypeOf(listener)
if listenerType != nil && listenerType.String() == "*tls.listener" {
return true
}
return false
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reflexion is slow, can't you use a type assertion?

server.go Outdated
Comment on lines 368 to 384
func WithListener(listener net.Listener) func(*Server) {
return func(s *Server) {
if s.listener != nil {
panic("a listener is already configured; cannot overwrite it")
}
s.listener = listener
s.Server.Addr = listener.Addr().String()
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code is way better, but using assertion seems overkill, also it's very slow.

@Ignaciojeria
Copy link
Author

Ignaciojeria commented Dec 2, 2024

To handle the overwriting of the listener or address, I have three options in mind:

  1. If the listener or address is overwritten, print a warning.
  2. Continue throwing a panic, stopping the process with a message.
  3. Create an array of errors as a private attribute in the server instance and handle them during setup.

What do you think is the best option, or do you have any suggestions?

@ccoVeille
Copy link
Collaborator

To handle the overwriting of the listener or address, I have three options in mind:

  1. If the listener or address is overwritten, print a warning.
  2. Continue throwing a panic, stopping the process with a message.
  3. Create an array of errors as a private attribute in the server instance and handle them during setup.

What do you think is the best option, or do you have any suggestions?

I would say no with the last option, it hides things. People may never calls it.

Throwing a panic is not my favorite either, but why not.

But first, why is there a problem to overload the listener if the server is not started?

Why not checking if the server is started to raise panic instead of checking s.listener ?

I mean for me there is the exact same issue with WithPort or WithAddress, you can overload until you started the server.

Another option would have been to use Option patrern that supports errors handling, but it's a bit late maybe. But maybe it was what you were suggesting with option 2, add an errServe to the struct, set in on error, check it after the for opt loop.

@dylanhitt
Copy link
Collaborator

dylanhitt commented Dec 2, 2024

I also don't understand the checking around Addr. The default listener should should be created with whatever value Server.Server.Addr is. We default when one isn't provided so it should always be set.

serve.go Outdated Show resolved Hide resolved
server.go Outdated Show resolved Hide resolved
server.go Outdated
s.OpenApiSpec.Servers = append(s.OpenApiSpec.Servers, &openapi3.Server{
URL: "http://" + s.Addr,
URL: fmt.Sprintf("%s://%s", s.proto(), s.Server.Addr),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this would always be just http at this point except in the case where a Listener is passed. The current behavior is that we don’t know until the user calls RunTLS which is fine.

I think we’d need even an even larger refactor to address this well. This falls out of scope of this PR

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm. Actually probably the proper places is at the end of setup().

I could be wrong it’s late 😅

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, I thought it could be a problem too.

So does it have to be merged like that? With somehow a bug?

Or the refactoring should gf done before? Either here or in another PR?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing it in setup now is fine.

server.go Outdated Show resolved Hide resolved
serve.go Show resolved Hide resolved
server.go Show resolved Hide resolved
server.go Outdated Show resolved Hide resolved
server.go Outdated Show resolved Hide resolved
server.go Outdated Show resolved Hide resolved
Copy link
Collaborator

@dylanhitt dylanhitt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last bit would be nice as it would setup the server stuff well. Also not sure why we need to know if the listener is TLS or not care to explain?

All in all thank you for the work. This PR was challenging and it didn't fit quite well into the patterns we've been sticking too but this looks good. 😄

It would be nice to have bit of organized commit history as I'm fan of commits being a body of work. We don't really have anything around here for commit guidelines yet so feel free to do as you wish unless someone expresses their opinions.

Thanks again

@Ignaciojeria
Copy link
Author

I removed the method that checks if the listener is TLS. Sorry if my commits are chaotic; I’m still learning. What do you think about canceling this pull request? I could create a new fork and consolidate all the changes into a single commit.

@ccoVeille
Copy link
Collaborator

I removed the method that checks if the listener is TLS. Sorry if my commits are chaotic; I’m still learning.

It's OK, really.

What do you think about canceling this pull request? I could create a new fork and consolidate all the changes into a single commit.

I would say keep the PR, it can be squashed in a single commit once merged.

If you still want to do it by your own, you can use git rebase -i and merge your commit into one.

But, from my perspective. It's better to have the history of changes in a PR than having it left behind, then another "clean but useless" one

Copy link
Collaborator

@dylanhitt dylanhitt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, git rebase -i HEAD~<number> is a great tool. I use it pretty much everyday.

Anywho, great work. It's awesome that you're learning by contributing to open source. I found that helped me more than anything.

Cheers!

serve.go Outdated Show resolved Hide resolved
@dylanhitt
Copy link
Collaborator

Thank you for the contribution. Seems you need to make lint and we should be good.

@Ignaciojeria
Copy link
Author

I still have pending unit tests as adding a couple of lines caused the coverage to drop. I'll review it more thoroughly this week after work.

server.go Outdated
Comment on lines 144 to 147
addr := s.Addr
if s.listener != nil {
addr = s.listener.Addr().String()
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Useless, isn't it? Can't we directly use s.Addr as it has been set in WithListener?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is possible for the user to do something poor here say the user adds their own listener and then their own withaddr. The value could be off since listener trumps everything. I plan on getting rid of this possibility with #248. Basically pushing the openapi server setup as close to startup as we can.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. Good thing mux and server are decorrelated in the std lib

serve.go Outdated Show resolved Hide resolved
@dylanhitt
Copy link
Collaborator

Hey @Ignaciojeria,

I added the tests and did come cleanup. I am gonna need you to rebase or you main branch though. (The rebase isn't the most fun either I just went through 😅). Here is the PR: Ignaciojeria#2

the two commits i added are:

I also could just open the PR myself. Idk whatever you want to do.

@dylanhitt
Copy link
Collaborator

hmm. It seems all commits are duplicated. Did you rebase locally?

@Ignaciojeria
Copy link
Author

Ignaciojeria commented Dec 20, 2024

hmm. It seems all commits are duplicated. Did you rebase locally?

Let me clean this up and push the updated branch

@dylanhitt
Copy link
Collaborator

Hey any update on this? 😄

@dylanhitt
Copy link
Collaborator

@EwenQuim when we go on this flurry of merges I think we should pull this over the line first.

…lid address in withTls

gosum included

Update all .mod files instead of only with-listener
@dylanhitt
Copy link
Collaborator

@EwenQuim bump on this. I’ve added commits so I don’t like to approve my own work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants