Web server using syscalls in Go
$begingroup$
I'm new to Go and wanted to implement a web server using system calls to get a better feel for how everything works.
I'm looking for feedback on idiomatic go especially error handling and using pointers.
I'm not particularly concerned about the completeness of the code. The primary goal was to learn syscalls in go.
package main
// Simple, single-threaded server using system calls instead of the net library.
//
// Omitted features from the go net package:
//
// - TLS
// - Most error checking
// - Only supports bodies that close, no persistent or chunked connections
// - Redirects
// - Deadlines and cancellation
// - Non-blocking sockets
import (
"bufio"
"errors"
"flag"
"io"
"log"
"net"
"net/textproto"
"os"
"strconv"
"strings"
"syscall"
)
// netSocket is a file descriptor for a system socket.
type netSocket struct {
// System file descriptor.
fd int
}
func (ns netSocket) Read(p byte) (int, error) {
if len(p) == 0 {
return 0, nil
}
n, err := syscall.Read(ns.fd, p)
if err != nil {
n = 0
}
return n, err
}
func (ns netSocket) Write(p byte) (int, error) {
n, err := syscall.Write(ns.fd, p)
if err != nil {
n = 0
}
return n, err
}
// Creates a new netSocket for the next pending connection request.
func (ns *netSocket) Accept() (*netSocket, error) {
// syscall.ForkLock doc states lock not needed for blocking accept.
nfd, _, err := syscall.Accept(ns.fd)
if err == nil {
syscall.CloseOnExec(nfd)
}
if err != nil {
return nil, err
}
return &netSocket{nfd}, nil
}
func (ns *netSocket) Close() error {
return syscall.Close(ns.fd)
}
// Creates a new socket file descriptor, binds it and listens on it.
func newNetSocket(ip net.IP, port int) (*netSocket, error) {
// ForkLock docs state that socket syscall requires the lock.
syscall.ForkLock.Lock()
// AF_INET = Address Family for IPv4
// SOCK_STREAM = virtual circuit service
// 0: the protocol for SOCK_STREAM, there's only 1.
fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, 0)
if err != nil {
return nil, os.NewSyscallError("socket", err)
}
syscall.ForkLock.Unlock()
// Allow reuse of recently-used addresses.
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
syscall.Close(fd)
return nil, os.NewSyscallError("setsockopt", err)
}
// Bind the socket to a port
sa := &syscall.SockaddrInet4{Port: port}
copy(sa.Addr[:], ip)
if err = syscall.Bind(fd, sa); err != nil {
return nil, os.NewSyscallError("bind", err)
}
// Listen for incoming connections.
if err = syscall.Listen(fd, syscall.SOMAXCONN); err != nil {
return nil, os.NewSyscallError("listen", err)
}
return &netSocket{fd: fd}, nil
}
type request struct {
method string // GET, POST, etc.
header textproto.MIMEHeader
body byte
uri string // The raw URI from the request
proto string // "HTTP/1.1"
}
func parseRequest(c *netSocket) (*request, error) {
b := bufio.NewReader(*c)
tp := textproto.NewReader(b)
req := new(request)
// First line: parse "GET /index.html HTTP/1.0"
var s string
s, _ = tp.ReadLine()
sp := strings.Split(s, " ")
req.method, req.uri, req.proto = sp[0], sp[1], sp[2]
// Parse headers
mimeHeader, _ := tp.ReadMIMEHeader()
req.header = mimeHeader
// Parse body
if req.method == "GET" || req.method == "HEAD" {
return req, nil
}
if len(req.header["Content-Length"]) == 0 {
return nil, errors.New("no content length")
}
length, err := strconv.Atoi(req.header["Content-Length"][0])
if err != nil {
return nil, err
}
body := make(byte, length)
if _, err = io.ReadFull(b, body); err != nil {
return nil, err
}
req.body = body
return req, nil
}
func main() {
ipFlag := flag.String("ip_addr", "127.0.0.1", "The IP address to use")
portFlag := flag.Int("port", 8080, "The port to use.")
flag.Parse()
ip := net.ParseIP(*ipFlag)
port := *portFlag
socket, err := newNetSocket(ip, port)
defer socket.Close()
if err != nil {
panic(err)
}
log.Print("===============")
log.Print("Server Started!")
log.Print("===============")
log.Print()
log.Printf("addr: http://%s:%d", ip, port)
for {
// Block until incoming connection
rw, e := socket.Accept()
log.Print()
log.Print()
log.Printf("Incoming connection")
if e != nil {
panic(e)
}
// Read request
log.Print("Reading request")
req, err := parseRequest(rw)
log.Print("request: ", req)
if err != nil {
panic(err)
}
// Write response
log.Print("Writing response")
io.WriteString(rw, "HTTP/1.1 200 OKrn"+
"Content-Type: text/html; charset=utf-8rn"+
"Content-Length: 20rn"+
"rn"+
"<h1>hello world</h1>")
if err != nil {
log.Print(err.Error())
continue
}
}
}
go networking socket server
New contributor
$endgroup$
add a comment |
$begingroup$
I'm new to Go and wanted to implement a web server using system calls to get a better feel for how everything works.
I'm looking for feedback on idiomatic go especially error handling and using pointers.
I'm not particularly concerned about the completeness of the code. The primary goal was to learn syscalls in go.
package main
// Simple, single-threaded server using system calls instead of the net library.
//
// Omitted features from the go net package:
//
// - TLS
// - Most error checking
// - Only supports bodies that close, no persistent or chunked connections
// - Redirects
// - Deadlines and cancellation
// - Non-blocking sockets
import (
"bufio"
"errors"
"flag"
"io"
"log"
"net"
"net/textproto"
"os"
"strconv"
"strings"
"syscall"
)
// netSocket is a file descriptor for a system socket.
type netSocket struct {
// System file descriptor.
fd int
}
func (ns netSocket) Read(p byte) (int, error) {
if len(p) == 0 {
return 0, nil
}
n, err := syscall.Read(ns.fd, p)
if err != nil {
n = 0
}
return n, err
}
func (ns netSocket) Write(p byte) (int, error) {
n, err := syscall.Write(ns.fd, p)
if err != nil {
n = 0
}
return n, err
}
// Creates a new netSocket for the next pending connection request.
func (ns *netSocket) Accept() (*netSocket, error) {
// syscall.ForkLock doc states lock not needed for blocking accept.
nfd, _, err := syscall.Accept(ns.fd)
if err == nil {
syscall.CloseOnExec(nfd)
}
if err != nil {
return nil, err
}
return &netSocket{nfd}, nil
}
func (ns *netSocket) Close() error {
return syscall.Close(ns.fd)
}
// Creates a new socket file descriptor, binds it and listens on it.
func newNetSocket(ip net.IP, port int) (*netSocket, error) {
// ForkLock docs state that socket syscall requires the lock.
syscall.ForkLock.Lock()
// AF_INET = Address Family for IPv4
// SOCK_STREAM = virtual circuit service
// 0: the protocol for SOCK_STREAM, there's only 1.
fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, 0)
if err != nil {
return nil, os.NewSyscallError("socket", err)
}
syscall.ForkLock.Unlock()
// Allow reuse of recently-used addresses.
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
syscall.Close(fd)
return nil, os.NewSyscallError("setsockopt", err)
}
// Bind the socket to a port
sa := &syscall.SockaddrInet4{Port: port}
copy(sa.Addr[:], ip)
if err = syscall.Bind(fd, sa); err != nil {
return nil, os.NewSyscallError("bind", err)
}
// Listen for incoming connections.
if err = syscall.Listen(fd, syscall.SOMAXCONN); err != nil {
return nil, os.NewSyscallError("listen", err)
}
return &netSocket{fd: fd}, nil
}
type request struct {
method string // GET, POST, etc.
header textproto.MIMEHeader
body byte
uri string // The raw URI from the request
proto string // "HTTP/1.1"
}
func parseRequest(c *netSocket) (*request, error) {
b := bufio.NewReader(*c)
tp := textproto.NewReader(b)
req := new(request)
// First line: parse "GET /index.html HTTP/1.0"
var s string
s, _ = tp.ReadLine()
sp := strings.Split(s, " ")
req.method, req.uri, req.proto = sp[0], sp[1], sp[2]
// Parse headers
mimeHeader, _ := tp.ReadMIMEHeader()
req.header = mimeHeader
// Parse body
if req.method == "GET" || req.method == "HEAD" {
return req, nil
}
if len(req.header["Content-Length"]) == 0 {
return nil, errors.New("no content length")
}
length, err := strconv.Atoi(req.header["Content-Length"][0])
if err != nil {
return nil, err
}
body := make(byte, length)
if _, err = io.ReadFull(b, body); err != nil {
return nil, err
}
req.body = body
return req, nil
}
func main() {
ipFlag := flag.String("ip_addr", "127.0.0.1", "The IP address to use")
portFlag := flag.Int("port", 8080, "The port to use.")
flag.Parse()
ip := net.ParseIP(*ipFlag)
port := *portFlag
socket, err := newNetSocket(ip, port)
defer socket.Close()
if err != nil {
panic(err)
}
log.Print("===============")
log.Print("Server Started!")
log.Print("===============")
log.Print()
log.Printf("addr: http://%s:%d", ip, port)
for {
// Block until incoming connection
rw, e := socket.Accept()
log.Print()
log.Print()
log.Printf("Incoming connection")
if e != nil {
panic(e)
}
// Read request
log.Print("Reading request")
req, err := parseRequest(rw)
log.Print("request: ", req)
if err != nil {
panic(err)
}
// Write response
log.Print("Writing response")
io.WriteString(rw, "HTTP/1.1 200 OKrn"+
"Content-Type: text/html; charset=utf-8rn"+
"Content-Length: 20rn"+
"rn"+
"<h1>hello world</h1>")
if err != nil {
log.Print(err.Error())
continue
}
}
}
go networking socket server
New contributor
$endgroup$
add a comment |
$begingroup$
I'm new to Go and wanted to implement a web server using system calls to get a better feel for how everything works.
I'm looking for feedback on idiomatic go especially error handling and using pointers.
I'm not particularly concerned about the completeness of the code. The primary goal was to learn syscalls in go.
package main
// Simple, single-threaded server using system calls instead of the net library.
//
// Omitted features from the go net package:
//
// - TLS
// - Most error checking
// - Only supports bodies that close, no persistent or chunked connections
// - Redirects
// - Deadlines and cancellation
// - Non-blocking sockets
import (
"bufio"
"errors"
"flag"
"io"
"log"
"net"
"net/textproto"
"os"
"strconv"
"strings"
"syscall"
)
// netSocket is a file descriptor for a system socket.
type netSocket struct {
// System file descriptor.
fd int
}
func (ns netSocket) Read(p byte) (int, error) {
if len(p) == 0 {
return 0, nil
}
n, err := syscall.Read(ns.fd, p)
if err != nil {
n = 0
}
return n, err
}
func (ns netSocket) Write(p byte) (int, error) {
n, err := syscall.Write(ns.fd, p)
if err != nil {
n = 0
}
return n, err
}
// Creates a new netSocket for the next pending connection request.
func (ns *netSocket) Accept() (*netSocket, error) {
// syscall.ForkLock doc states lock not needed for blocking accept.
nfd, _, err := syscall.Accept(ns.fd)
if err == nil {
syscall.CloseOnExec(nfd)
}
if err != nil {
return nil, err
}
return &netSocket{nfd}, nil
}
func (ns *netSocket) Close() error {
return syscall.Close(ns.fd)
}
// Creates a new socket file descriptor, binds it and listens on it.
func newNetSocket(ip net.IP, port int) (*netSocket, error) {
// ForkLock docs state that socket syscall requires the lock.
syscall.ForkLock.Lock()
// AF_INET = Address Family for IPv4
// SOCK_STREAM = virtual circuit service
// 0: the protocol for SOCK_STREAM, there's only 1.
fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, 0)
if err != nil {
return nil, os.NewSyscallError("socket", err)
}
syscall.ForkLock.Unlock()
// Allow reuse of recently-used addresses.
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
syscall.Close(fd)
return nil, os.NewSyscallError("setsockopt", err)
}
// Bind the socket to a port
sa := &syscall.SockaddrInet4{Port: port}
copy(sa.Addr[:], ip)
if err = syscall.Bind(fd, sa); err != nil {
return nil, os.NewSyscallError("bind", err)
}
// Listen for incoming connections.
if err = syscall.Listen(fd, syscall.SOMAXCONN); err != nil {
return nil, os.NewSyscallError("listen", err)
}
return &netSocket{fd: fd}, nil
}
type request struct {
method string // GET, POST, etc.
header textproto.MIMEHeader
body byte
uri string // The raw URI from the request
proto string // "HTTP/1.1"
}
func parseRequest(c *netSocket) (*request, error) {
b := bufio.NewReader(*c)
tp := textproto.NewReader(b)
req := new(request)
// First line: parse "GET /index.html HTTP/1.0"
var s string
s, _ = tp.ReadLine()
sp := strings.Split(s, " ")
req.method, req.uri, req.proto = sp[0], sp[1], sp[2]
// Parse headers
mimeHeader, _ := tp.ReadMIMEHeader()
req.header = mimeHeader
// Parse body
if req.method == "GET" || req.method == "HEAD" {
return req, nil
}
if len(req.header["Content-Length"]) == 0 {
return nil, errors.New("no content length")
}
length, err := strconv.Atoi(req.header["Content-Length"][0])
if err != nil {
return nil, err
}
body := make(byte, length)
if _, err = io.ReadFull(b, body); err != nil {
return nil, err
}
req.body = body
return req, nil
}
func main() {
ipFlag := flag.String("ip_addr", "127.0.0.1", "The IP address to use")
portFlag := flag.Int("port", 8080, "The port to use.")
flag.Parse()
ip := net.ParseIP(*ipFlag)
port := *portFlag
socket, err := newNetSocket(ip, port)
defer socket.Close()
if err != nil {
panic(err)
}
log.Print("===============")
log.Print("Server Started!")
log.Print("===============")
log.Print()
log.Printf("addr: http://%s:%d", ip, port)
for {
// Block until incoming connection
rw, e := socket.Accept()
log.Print()
log.Print()
log.Printf("Incoming connection")
if e != nil {
panic(e)
}
// Read request
log.Print("Reading request")
req, err := parseRequest(rw)
log.Print("request: ", req)
if err != nil {
panic(err)
}
// Write response
log.Print("Writing response")
io.WriteString(rw, "HTTP/1.1 200 OKrn"+
"Content-Type: text/html; charset=utf-8rn"+
"Content-Length: 20rn"+
"rn"+
"<h1>hello world</h1>")
if err != nil {
log.Print(err.Error())
continue
}
}
}
go networking socket server
New contributor
$endgroup$
I'm new to Go and wanted to implement a web server using system calls to get a better feel for how everything works.
I'm looking for feedback on idiomatic go especially error handling and using pointers.
I'm not particularly concerned about the completeness of the code. The primary goal was to learn syscalls in go.
package main
// Simple, single-threaded server using system calls instead of the net library.
//
// Omitted features from the go net package:
//
// - TLS
// - Most error checking
// - Only supports bodies that close, no persistent or chunked connections
// - Redirects
// - Deadlines and cancellation
// - Non-blocking sockets
import (
"bufio"
"errors"
"flag"
"io"
"log"
"net"
"net/textproto"
"os"
"strconv"
"strings"
"syscall"
)
// netSocket is a file descriptor for a system socket.
type netSocket struct {
// System file descriptor.
fd int
}
func (ns netSocket) Read(p byte) (int, error) {
if len(p) == 0 {
return 0, nil
}
n, err := syscall.Read(ns.fd, p)
if err != nil {
n = 0
}
return n, err
}
func (ns netSocket) Write(p byte) (int, error) {
n, err := syscall.Write(ns.fd, p)
if err != nil {
n = 0
}
return n, err
}
// Creates a new netSocket for the next pending connection request.
func (ns *netSocket) Accept() (*netSocket, error) {
// syscall.ForkLock doc states lock not needed for blocking accept.
nfd, _, err := syscall.Accept(ns.fd)
if err == nil {
syscall.CloseOnExec(nfd)
}
if err != nil {
return nil, err
}
return &netSocket{nfd}, nil
}
func (ns *netSocket) Close() error {
return syscall.Close(ns.fd)
}
// Creates a new socket file descriptor, binds it and listens on it.
func newNetSocket(ip net.IP, port int) (*netSocket, error) {
// ForkLock docs state that socket syscall requires the lock.
syscall.ForkLock.Lock()
// AF_INET = Address Family for IPv4
// SOCK_STREAM = virtual circuit service
// 0: the protocol for SOCK_STREAM, there's only 1.
fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, 0)
if err != nil {
return nil, os.NewSyscallError("socket", err)
}
syscall.ForkLock.Unlock()
// Allow reuse of recently-used addresses.
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
syscall.Close(fd)
return nil, os.NewSyscallError("setsockopt", err)
}
// Bind the socket to a port
sa := &syscall.SockaddrInet4{Port: port}
copy(sa.Addr[:], ip)
if err = syscall.Bind(fd, sa); err != nil {
return nil, os.NewSyscallError("bind", err)
}
// Listen for incoming connections.
if err = syscall.Listen(fd, syscall.SOMAXCONN); err != nil {
return nil, os.NewSyscallError("listen", err)
}
return &netSocket{fd: fd}, nil
}
type request struct {
method string // GET, POST, etc.
header textproto.MIMEHeader
body byte
uri string // The raw URI from the request
proto string // "HTTP/1.1"
}
func parseRequest(c *netSocket) (*request, error) {
b := bufio.NewReader(*c)
tp := textproto.NewReader(b)
req := new(request)
// First line: parse "GET /index.html HTTP/1.0"
var s string
s, _ = tp.ReadLine()
sp := strings.Split(s, " ")
req.method, req.uri, req.proto = sp[0], sp[1], sp[2]
// Parse headers
mimeHeader, _ := tp.ReadMIMEHeader()
req.header = mimeHeader
// Parse body
if req.method == "GET" || req.method == "HEAD" {
return req, nil
}
if len(req.header["Content-Length"]) == 0 {
return nil, errors.New("no content length")
}
length, err := strconv.Atoi(req.header["Content-Length"][0])
if err != nil {
return nil, err
}
body := make(byte, length)
if _, err = io.ReadFull(b, body); err != nil {
return nil, err
}
req.body = body
return req, nil
}
func main() {
ipFlag := flag.String("ip_addr", "127.0.0.1", "The IP address to use")
portFlag := flag.Int("port", 8080, "The port to use.")
flag.Parse()
ip := net.ParseIP(*ipFlag)
port := *portFlag
socket, err := newNetSocket(ip, port)
defer socket.Close()
if err != nil {
panic(err)
}
log.Print("===============")
log.Print("Server Started!")
log.Print("===============")
log.Print()
log.Printf("addr: http://%s:%d", ip, port)
for {
// Block until incoming connection
rw, e := socket.Accept()
log.Print()
log.Print()
log.Printf("Incoming connection")
if e != nil {
panic(e)
}
// Read request
log.Print("Reading request")
req, err := parseRequest(rw)
log.Print("request: ", req)
if err != nil {
panic(err)
}
// Write response
log.Print("Writing response")
io.WriteString(rw, "HTTP/1.1 200 OKrn"+
"Content-Type: text/html; charset=utf-8rn"+
"Content-Length: 20rn"+
"rn"+
"<h1>hello world</h1>")
if err != nil {
log.Print(err.Error())
continue
}
}
}
go networking socket server
go networking socket server
New contributor
New contributor
New contributor
asked 9 mins ago
JoeJoe
101
101
New contributor
New contributor
add a comment |
add a comment |
0
active
oldest
votes
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Joe is a new contributor. Be nice, and check out our Code of Conduct.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f213643%2fweb-server-using-syscalls-in-go%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
0
active
oldest
votes
0
active
oldest
votes
active
oldest
votes
active
oldest
votes
Joe is a new contributor. Be nice, and check out our Code of Conduct.
Joe is a new contributor. Be nice, and check out our Code of Conduct.
Joe is a new contributor. Be nice, and check out our Code of Conduct.
Joe is a new contributor. Be nice, and check out our Code of Conduct.
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f213643%2fweb-server-using-syscalls-in-go%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown