Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions granian/_granian.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ if sys.platform != 'win32':
def __new__(cls, bind: str, backlog: int, permissions: int | None) -> UnixListenerSpec: ...
def build(self) -> SocketHolder: ...
def is_uds(self) -> bool: ...
class FdListenerSpec:
def __new__(cls, fd: int) -> FdListenerSpec: ...
def build(self) -> SocketHolder: ...
def is_uds(self) -> bool: ...

class CallbackScheduler:
_loop: Any
Expand Down
3 changes: 3 additions & 0 deletions granian/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ def option(*param_decls: str, cls: type[click.Option] | None = None, **attrs: An
default=Interfaces.RSGI,
help='Application interface type',
)
@option('--fd', type=int, default=None, help='Listen on socket from this file-descriptor.')
@option('--http', type=EnumType(HTTPModes), default=HTTPModes.auto, help='HTTP version')
@option('--ws/--no-ws', 'websockets', default=True, help='Enable websockets handling')
@option('--workers', type=click.IntRange(1), default=1, help='Number of worker processes')
Expand Down Expand Up @@ -438,6 +439,7 @@ def cli(
port: int,
uds: pathlib.Path | None,
uds_permissions: int | None,
fd: int | None,
interface: Interfaces,
http: HTTPModes,
websockets: bool,
Expand Down Expand Up @@ -524,6 +526,7 @@ def cli(
port=port,
uds=uds,
uds_permissions=uds_permissions,
fd=fd,
interface=interface,
workers=workers,
blocking_threads=blocking_threads,
Expand Down
4 changes: 3 additions & 1 deletion granian/net.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
copyreg.pickle(SocketSpec, lambda v: (SocketSpec, v.__getstate__()))

if sys.platform != 'win32':
from ._granian import UnixListenerSpec as UnixSocketSpec
from ._granian import UnixListenerSpec as UnixSocketSpec, FdListenerSpec as FdSocketSpec

copyreg.pickle(UnixSocketSpec, lambda v: (UnixSocketSpec, v.__getstate__()))
copyreg.pickle(FdSocketSpec, lambda v: (FdSocketSpec, v.__getstate__()))
else:
UnixSocketSpec = None
FdSocketSpec = None
15 changes: 12 additions & 3 deletions granian/server/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from ..errors import ConfigurationError, PidFileError
from ..http import HTTP1Settings, HTTP2Settings
from ..log import DEFAULT_ACCESSLOG_FMT, LogLevels, configure_logging, logger
from ..net import SocketSpec, UnixSocketSpec
from ..net import SocketSpec, UnixSocketSpec, FdSocketSpec


WT = TypeVar('WT')
Expand Down Expand Up @@ -87,6 +87,7 @@ def __init__(
port: int = 8000,
uds: Path | None = None,
uds_permissions: int | None = None,
fd: int | None = None,
interface: Interfaces = Interfaces.RSGI,
workers: int = 1,
blocking_threads: int | None = None,
Expand Down Expand Up @@ -149,6 +150,7 @@ def __init__(
self.bind_port = port
self.bind_uds = uds.resolve() if uds else None
self.uds_permissions = uds_permissions
self.bind_fd = fd
self.interface = interface
self.workers = max(1, workers)
self.runtime_threads = max(1, runtime_threads)
Expand Down Expand Up @@ -291,7 +293,12 @@ def build_ssl_context(

@property
def _bind_addr_fmt(self):
return f'unix:{self.bind_uds}' if self.bind_uds else f'{self.bind_addr}:{self.bind_port}'
if self.bind_fd is not None:
return f'fd:{self.bind_fd}'
elif self.bind_uds :
return f'unix:{self.bind_uds}'
else:
f'{self.bind_addr}:{self.bind_port}'

@staticmethod
def _call_hooks(hooks):
Expand All @@ -311,7 +318,9 @@ def on_shutdown(self, hook: Callable[[], Any]) -> Callable[[], Any]:
return hook

def _init_shared_socket(self):
if self.bind_uds:
if self.bind_fd:
self._ssp = FdSocketSpec(self.bind_fd)
elif self.bind_uds:
self._ssp = UnixSocketSpec(str(self.bind_uds), self.backlog, self.uds_permissions)
else:
self._ssp = SocketSpec(self.bind_addr, self.bind_port, self.backlog)
Expand Down
58 changes: 58 additions & 0 deletions src/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ pub struct UnixListenerSpec {
permissions: Option<u32>,
}


#[cfg(unix)]
#[pyclass(frozen, module = "granian._granian")]
#[derive(Clone)]
pub struct FdListenerSpec {
inp: (i32,),
fd: i32,
}

impl ListenerSpec {
pub(crate) fn as_socket(&self) -> Result<Socket> {
let socket = Socket::new(self.domain, Type::STREAM, Some(Protocol::TCP))?;
Expand Down Expand Up @@ -161,6 +170,35 @@ impl UnixListenerSpec {
}
}


#[cfg(unix)]
impl FdListenerSpec {
pub(crate) fn as_socket(&self) -> Result<Socket> {
let socket = unsafe { Socket::from_raw_fd(self.fd) };
Ok(socket)
}
}

#[cfg(unix)]
#[pymethods]
impl FdListenerSpec {
#[new]
fn new(fd: i32) -> PyResult<Self> {
Ok(Self {
inp: (fd,),
fd,
})
}

fn build(&self) -> Result<SocketHolder> {
SocketHolder::from_fd_spec(self)
}

pub fn __getstate__(&self, py: Python) -> Py<PyAny> {
self.inp.clone().into_py_any(py).unwrap()
}
}

#[cfg(not(any(windows, target_os = "linux", target_os = "freebsd")))]
#[pyclass(frozen, module = "granian._granian")]
pub struct SocketHolder {
Expand All @@ -186,6 +224,15 @@ impl SocketHolder {
})
}

fn from_fd_spec(spec: &FdListenerSpec) -> Result<Self> {
let socket = spec.as_socket()?;
let domain = socket.domain().unwrap();
Ok(Self {
socket: Some(socket),
uds: domain == Domain::UNIX,
})
}

#[allow(clippy::unnecessary_wraps)]
pub fn as_tcp_listener(&self) -> Result<TcpListener> {
let listener = unsafe { TcpListener::from_raw_fd(self.socket.as_ref().unwrap().as_raw_fd()) };
Expand Down Expand Up @@ -260,6 +307,16 @@ impl SocketHolder {
})
}

fn from_fd_spec(spec: &FdListenerSpec) -> Result<Self> {
let socket = spec.as_socket()?;
let domain = socket.domain().unwrap();
Ok(Self {
socket: Some(socket),
uds: domain == Domain::UNIX,
backlog: 20,
})
}

pub fn as_tcp_listener(&self) -> Result<TcpListener> {
let socket = self.socket.as_ref().unwrap();
socket.listen(self.backlog)?;
Expand Down Expand Up @@ -355,6 +412,7 @@ pub(crate) fn init_pymodule(module: &Bound<PyModule>) -> PyResult<()> {
module.add_class::<SocketHolder>()?;
#[cfg(unix)]
module.add_class::<UnixListenerSpec>()?;
module.add_class::<FdListenerSpec>()?;

Ok(())
}