AngelMunoz / Perla

A cross-platform tool for unbundled front-end development that doesn't depend on Node or requires you to install a complex toolchain
https://perla-docs.web.app/
MIT License
132 stars 11 forks source link

Use alternate address if default address is already in use #45

Closed zeshhaan closed 2 years ago

zeshhaan commented 2 years ago

Consider this feature request coming from an early-stage dev so i don't have much knowledge in configuring ports manually 😅

Is your feature request related to a problem? Please describe. I was wondering why the server didn't spin up, then realised that i have been running one on the same port address 💡

Describe the solution you'd like Ability to detect if an address is already in use and if then automatically fallback to a different address to spin up the dev server.

Describe alternatives you've considered Quit current server, run the other one, quit that one, run the first one.

Additional context

This is the message that is being logged when i run dotnet run --project ../Perla -- serve

Checking esbuild is present...
esbuild is present.
Starting Dev Server
Unhandled exception. System.AggregateException: One or more errors occurred. (Failed to bind to address http://127.0.0.1:7331: address already in use.)
 ---> System.IO.IOException: Failed to bind to address http://127.0.0.1:7331: address already in use.
 ---> Microsoft.AspNetCore.Connections.AddressInUseException: Address already in use
 ---> System.Net.Sockets.SocketException (48): Address already in use
   at System.Net.Sockets.Socket.UpdateStatusAfterSocketErrorAndThrowException(SocketError error, String callerName)
   at System.Net.Sockets.Socket.DoBind(EndPoint endPointSnapshot, SocketAddress socketAddress)
   at System.Net.Sockets.Socket.Bind(EndPoint localEP)
   at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportOptions.CreateDefaultBoundListenSocket(EndPoint endpoint)
   at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketConnectionListener.Bind()
   --- End of inner exception stack trace ---
   at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketConnectionListener.Bind()
   at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportFactory.BindAsync(EndPoint endpoint, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.TransportManager.BindAsync(EndPoint endPoint, ConnectionDelegate connectionDelegate, EndpointConfig endpointConfig, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.<>c__DisplayClass30_0`1.<<StartAsync>g__OnBind|0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Server.Kestrel.Core.LocalhostListenOptions.BindAsync(AddressBindContext context, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.AddressesStrategy.BindAsync(AddressBindContext context, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindAsync(IEnumerable`1 listenOptions, AddressBindContext context, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.BindAsync(CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken)
   at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken)
   at Perla.Server.startServer@485.Invoke(Unit unitVar) in /Volumes/Work/oss/perla-forked/Perla/src/Perla/Server.fs:line 485
   at Ply.TplPrimitives.ContinuationStateMachine`1.System-Runtime-CompilerServices-IAsyncStateMachine-MoveNext()
   --- End of inner exception stack trace ---
   at Microsoft.FSharp.Control.AsyncPrimitives.Start@1077-1.Invoke(ExceptionDispatchInfo edi)
   at Perla.Server.Pipe #1 input at line 500@502-3.Invoke(AsyncActivation`1 ctxt) in /Volumes/Work/oss/perla-forked/Perla/src/Perla/Server.fs:line 502
   at Microsoft.FSharp.Control.Trampoline.Execute(FSharpFunc`2 firstAction) in D:\workspace\_work\1\s\src\fsharp\FSharp.Core\async.fs:line 104
   at <StartupCode$FSharp-Core>.$Async.clo@181-16.Invoke(Object o) in D:\workspace\_work\1\s\src\fsharp\FSharp.Core\async.fs:line 183
   at System.Threading.QueueUserWorkItemCallbackDefaultContext.Execute()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
   at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()
   at System.Threading.Thread.StartCallback()
AngelMunoz commented 2 years ago

Hello there!

I'm not sure how can we do that, but we could at least ensure that the error is readable in the meantime, you can change your server port in perla.jsonc

{
  "$schema": "https://raw.githubusercontent.com/AngelMunoz/Perla/main/perla.schema.json",
  "index": "./index.html",
  "devServer": {
    "port": 3000 // <- right here
    ... content ...
  },
  "build": { ... content ... },
  "packages": { ... content ...}
}

thanks for reporting it

zeshhaan commented 2 years ago

Thank you! I will check that way out next time.

Vite and other similar tools has this out of the box so i thought this might be a "good to have" sometime in the future.

AngelMunoz commented 2 years ago

Yes, this sounds completely reasonable I will keep this one open

AngelMunoz commented 2 years ago

I've been playing with this looks like this might be the way to do it

#!/usr/bin/env -S dotnet fsi

open System.Net
open System.Net.NetworkInformation

let props = IPGlobalProperties.GetIPGlobalProperties()

let listeners = props.GetActiveTcpListeners()

let findByPortAndAddress (address: string) (port: int) (listener: IPEndPoint) =
    let (didParse, address) = IPEndPoint.TryParse($"{address}:{port}")

    if didParse then
        listener.Address = address.Address
        && address.Port = listener.Port
    else
        false

let found =
    listeners
    |> Array.tryFind (findByPortAndAddress "127.0.0.1" 7331)

printfn "Found: %A" found

We would only need to put this detection code before the server is configured, would you be interested in trying to implement it?

otherwise let me know :) and I'll go for it

zeshhaan commented 2 years ago

Great news!

I would like to check this for myself but i fear i wouldn't have the time until the weekend. I would recommend you to go for it.

AngelMunoz commented 2 years ago

This is out in v0.16.0