This answer caters for usernames and passwords too:
/^(\w+):\/\/(?:([^:@\/]*)(?::([^@\/]+)|)\@|)((?:[a-zA-Z0-9]+\.|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]\.)*(?:[a-zA-Z0-9]+|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]))(?::(\d{1,5})|)(.*)/o
e.g.
#!perl
use strict;
use warnings;
for my $uri( qw(https://www.example.de
http://www.example.de
https://example.de
http://example.de
www.example.de
example.de:123
http://www.example.de:445/can?this=happen&too=1#lalala
http://www.example.de/can?this=happen&too=1#foo
http://www.example.de:445
wss://stream.binance.com:9443/stream?streams=xrpbtc@kl
+ine_1m/ethbtc@kline_1m/btcusdt@kline_1m
http://a:b@example.com:890/path/wah@t/foo.js?foo=bar&b
+ingobang=&king=kong@kong.com#foobar/bing/bo@ng?bang"
ftp://username@hostname/
ftp://username:password@hostname/
)
) {
print "in ($uri):\n";
my @parts=($uri=~/^(\w+):\/\/ # scheme (ftp http wss etc)
(?:([^:@\/]*) # optional username
(?::([^@\/]+)|) # optional password
\@|) # username and password are op
+tional
( # group all the bits of the UR
+L and its dots
(?:[a-zA-Z0-9]+\.|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-
+Z0-9]\.)*(?:[a-zA-Z0-9]+|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])
)
(?::(\d{1,5})|) # optional port
(.*)/xo); # path and query parms come la
+st
for(my $i=0;$i<=$#parts;$i++) { print " $i: $parts[$i]\n" if($parts
+[$i]); }
}