├── .github └── workflows │ └── test.yml ├── .gitignore ├── .pre-commit-config.yaml ├── LICENSE ├── README.md ├── external ├── __init__.mojo ├── gojo │ ├── __init__.mojo │ ├── builtins │ │ ├── __init__.mojo │ │ ├── attributes.mojo │ │ ├── bytes.mojo │ │ └── errors.mojo │ ├── io │ │ ├── __init__.mojo │ │ ├── io.mojo │ │ └── traits.mojo │ ├── net │ │ ├── __init__.mojo │ │ ├── address.mojo │ │ ├── dial.mojo │ │ ├── fd.mojo │ │ ├── ip.mojo │ │ ├── net.mojo │ │ ├── socket.mojo │ │ └── tcp.mojo │ ├── strings │ │ ├── __init__.mojo │ │ ├── builder.mojo │ │ └── reader.mojo │ └── syscall │ │ ├── __init__.mojo │ │ ├── file.mojo │ │ └── net.mojo └── libc.mojo ├── http_client ├── __init__.mojo ├── client.mojo ├── response.mojo └── uri.mojo └── tests ├── __init__.mojo ├── test_client.mojo ├── test_uri.mojo └── wrapper.mojo /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | 3 | on: ["push"] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | environment: basic 9 | steps: 10 | - name: Check out repository code 11 | uses: actions/checkout@v2 12 | - name: Install dependencies 13 | run: | 14 | curl https://get.modular.com | MODULAR_AUTH=${{ secrets.MODULAR_AUTH }} sh - 15 | modular auth ${{ secrets.MODULAR_AUTH }} 16 | modular install mojo 17 | pip install pytest 18 | pip install git+https://github.com/guidorice/mojo-pytest.git 19 | - name: Unit Tests 20 | run: | 21 | export MODULAR_HOME="/home/runner/.modular" 22 | export PATH="/home/runner/.modular/pkg/packages.modular.com_mojo/bin:$PATH" 23 | pytest 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v2.3.0 4 | hooks: 5 | - id: check-yaml 6 | - id: end-of-file-fixer 7 | - id: trailing-whitespace 8 | - repo: local 9 | hooks: 10 | - id: mojo-format 11 | name: mojo-format 12 | entry: mojo format -l 120 13 | language: system 14 | files: '\.(mojo|🔥)$' 15 | stages: [commit] 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Mikhail Tavarez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mojo-http-client 2 | 3 | ![Mojo 24.4](https://img.shields.io/badge/Mojo%F0%9F%94%A5-24.4-purple) 4 | 5 | A barebones HTTP/1.1 client for Mojo using only Mojo and external C calls. 6 | 7 | Thanks to the following for a large chunk of the code for working with sockets via external calls to C! 8 | 9 | - https://github.com/saviorand/lightbug_http/tree/main 10 | - https://github.com/gabrieldemarmiesse/mojo-stdlib-extensions/tree/master 11 | 12 | ## Usage 13 | 14 | Currently, it's a simple client. It's able to send requests and parse response strings into a Response struct, and pass some data along. 15 | 16 | 17 | ```mojo 18 | from http_client.client import HTTPClient, Headers 19 | 20 | 21 | fn test_post() raises: 22 | var test = MojoTest("Testing client.post") 23 | 24 | # Add headers 25 | var headers = Headers() 26 | headers["Connection"] = "close" 27 | 28 | # Add data 29 | var data = Dict[String, String]() 30 | data["hello"] = "world" 31 | 32 | var response = HTTPClient().post("www.httpbin.org", "/post", headers=headers, data=data) 33 | test.assert_equal(response.status_code, 200) 34 | test.assert_equal(response.status_message, "OK") 35 | test.assert_equal(response.headers["Content-Type"], "application/json") 36 | test.assert_equal(response.scheme, "http") 37 | 38 | 39 | # Simple GET request 40 | fn test_get() raises: 41 | var test = MojoTest("Testing client.get") 42 | var response = HTTPClient().get("www.example.com", "/", 80) 43 | print(response) 44 | test.assert_equal(response.status_code, 200) 45 | test.assert_equal(response.status_message, "OK") 46 | test.assert_equal(response.scheme, "http") 47 | ``` 48 | 49 | ## TODO 50 | 51 | - Add SSL support 52 | - Add HTTP/2 support 53 | - Add tests 54 | - Fix URI query params logic. String termination messes up the host name. 55 | -------------------------------------------------------------------------------- /external/__init__.mojo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thatstoasty/mojo-http-client/b377403387e32825d6cc029fa7054ae1ff0876aa/external/__init__.mojo -------------------------------------------------------------------------------- /external/gojo/__init__.mojo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thatstoasty/mojo-http-client/b377403387e32825d6cc029fa7054ae1ff0876aa/external/gojo/__init__.mojo -------------------------------------------------------------------------------- /external/gojo/builtins/__init__.mojo: -------------------------------------------------------------------------------- 1 | from .bytes import Byte, index_byte, has_suffix, has_prefix, to_string 2 | from .attributes import cap, copy 3 | from .errors import exit, panic 4 | 5 | alias Rune = Int32 6 | -------------------------------------------------------------------------------- /external/gojo/builtins/attributes.mojo: -------------------------------------------------------------------------------- 1 | fn copy[T: CollectionElement](inout target: List[T], source: List[T], start: Int = 0) -> Int: 2 | """Copies the contents of source into target at the same index. Returns the number of bytes copied. 3 | Added a start parameter to specify the index to start copying into. 4 | 5 | Args: 6 | target: The buffer to copy into. 7 | source: The buffer to copy from. 8 | start: The index to start copying into. 9 | 10 | Returns: 11 | The number of bytes copied. 12 | """ 13 | var count = 0 14 | 15 | for i in range(len(source)): 16 | if i + start > len(target): 17 | target[i + start] = source[i] 18 | else: 19 | target.append(source[i]) 20 | count += 1 21 | 22 | return count 23 | 24 | 25 | fn copy( 26 | inout target: List[UInt8], 27 | source: DTypePointer[DType.uint8], 28 | source_start: Int, 29 | source_end: Int, 30 | target_start: Int = 0, 31 | ) -> Int: 32 | """Copies the contents of source into target at the same index. Returns the number of bytes copied. 33 | Added a start parameter to specify the index to start copying into. 34 | 35 | Args: 36 | target: The buffer to copy into. 37 | source: The buffer to copy from. 38 | source_start: The index to start copying from. 39 | source_end: The index to stop copying at. 40 | target_start: The index to start copying into. 41 | 42 | Returns: 43 | The number of bytes copied. 44 | """ 45 | var count = 0 46 | 47 | for i in range(source_start, source_end): 48 | if i + target_start > len(target): 49 | target[i + target_start] = source[i] 50 | else: 51 | target.append(source[i]) 52 | count += 1 53 | 54 | return count 55 | 56 | 57 | # fn copy[T: CollectionElement](inout target: Span[T], source: Span[T], start: Int = 0) -> Int: 58 | # """Copies the contents of source into target at the same index. Returns the number of bytes copied. 59 | # Added a start parameter to specify the index to start copying into. 60 | 61 | # Args: 62 | # target: The buffer to copy into. 63 | # source: The buffer to copy from. 64 | # start: The index to start copying into. 65 | 66 | # Returns: 67 | # The number of bytes copied. 68 | # """ 69 | # var count = 0 70 | 71 | # for i in range(len(source)): 72 | # target[i + start] = source[i] 73 | # count += 1 74 | 75 | # return count 76 | 77 | 78 | fn cap[T: CollectionElement](iterable: List[T]) -> Int: 79 | """Returns the capacity of the List. 80 | 81 | Args: 82 | iterable: The List to get the capacity of. 83 | """ 84 | return iterable.capacity 85 | -------------------------------------------------------------------------------- /external/gojo/builtins/bytes.mojo: -------------------------------------------------------------------------------- 1 | alias Byte = UInt8 2 | 3 | 4 | fn equals(left: List[UInt8], right: List[UInt8]) -> Bool: 5 | if len(left) != len(right): 6 | return False 7 | for i in range(len(left)): 8 | if left[i] != right[i]: 9 | return False 10 | return True 11 | 12 | 13 | fn has_prefix(bytes: List[Byte], prefix: List[Byte]) -> Bool: 14 | """Reports whether the List[Byte] struct begins with prefix. 15 | 16 | Args: 17 | bytes: The List[Byte] struct to search. 18 | prefix: The prefix to search for. 19 | 20 | Returns: 21 | True if the List[Byte] struct begins with prefix; otherwise, False. 22 | """ 23 | var len_comparison = len(bytes) >= len(prefix) 24 | var prefix_comparison = equals(bytes[0 : len(prefix)], prefix) 25 | return len_comparison and prefix_comparison 26 | 27 | 28 | fn has_suffix(bytes: List[Byte], suffix: List[Byte]) -> Bool: 29 | """Reports whether the List[Byte] struct ends with suffix. 30 | 31 | Args: 32 | bytes: The List[Byte] struct to search. 33 | suffix: The prefix to search for. 34 | 35 | Returns: 36 | True if the List[Byte] struct ends with suffix; otherwise, False. 37 | """ 38 | var len_comparison = len(bytes) >= len(suffix) 39 | var suffix_comparison = equals(bytes[len(bytes) - len(suffix) : len(bytes)], suffix) 40 | return len_comparison and suffix_comparison 41 | 42 | 43 | fn index_byte(bytes: List[Byte], delim: Byte) -> Int: 44 | """Return the index of the first occurrence of the byte delim. 45 | 46 | Args: 47 | bytes: The List[Byte] struct to search. 48 | delim: The byte to search for. 49 | 50 | Returns: 51 | The index of the first occurrence of the byte delim. 52 | """ 53 | for i in range(len(bytes)): 54 | if bytes[i] == delim: 55 | return i 56 | 57 | return -1 58 | 59 | 60 | fn index_byte(bytes: DTypePointer[DType.uint8], size: Int, delim: Byte) -> Int: 61 | """Return the index of the first occurrence of the byte delim. 62 | 63 | Args: 64 | bytes: The DTypePointer[DType.int8] struct to search. 65 | size: The size of the bytes pointer. 66 | delim: The byte to search for. 67 | 68 | Returns: 69 | The index of the first occurrence of the byte delim. 70 | """ 71 | for i in range(size): 72 | if UInt8(bytes[i]) == delim: 73 | return i 74 | 75 | return -1 76 | 77 | 78 | fn to_string(bytes: List[Byte]) -> String: 79 | """Makes a deepcopy of the List[Byte] supplied and converts it to a string. If it's not null terminated, it will append a null byte. 80 | 81 | Args: 82 | bytes: The List[Byte] struct to convert. 83 | 84 | Returns: 85 | The string representation of the List[Byte] struct. 86 | """ 87 | var copy = List[Byte](bytes) 88 | if copy[-1] != 0: 89 | copy.append(0) 90 | return String(copy) 91 | -------------------------------------------------------------------------------- /external/gojo/builtins/errors.mojo: -------------------------------------------------------------------------------- 1 | from sys import exit 2 | 3 | 4 | fn panic[T: Stringable](message: T, code: Int = 1): 5 | """Panics the program with the given message and exit code. 6 | 7 | Args: 8 | message: The message to panic with. 9 | code: The exit code to panic with. 10 | """ 11 | print("panic:", message) 12 | exit(code) 13 | -------------------------------------------------------------------------------- /external/gojo/io/__init__.mojo: -------------------------------------------------------------------------------- 1 | from .traits import ( 2 | Reader, 3 | Writer, 4 | Seeker, 5 | Closer, 6 | ReadWriter, 7 | ReadCloser, 8 | WriteCloser, 9 | ReadWriteCloser, 10 | ReadSeeker, 11 | ReadSeekCloser, 12 | WriteSeeker, 13 | ReadWriteSeeker, 14 | ReaderFrom, 15 | WriterReadFrom, 16 | WriterTo, 17 | ReaderWriteTo, 18 | ReaderAt, 19 | WriterAt, 20 | ByteReader, 21 | ByteScanner, 22 | ByteWriter, 23 | RuneReader, 24 | RuneScanner, 25 | StringWriter, 26 | SEEK_START, 27 | SEEK_CURRENT, 28 | SEEK_END, 29 | ERR_SHORT_WRITE, 30 | ERR_NO_PROGRESS, 31 | ERR_SHORT_BUFFER, 32 | EOF, 33 | ) 34 | from .io import write_string, read_at_least, read_full, read_all, BUFFER_SIZE 35 | 36 | 37 | alias i1 = __mlir_type.i1 38 | alias i1_1 = __mlir_attr.`1: i1` 39 | alias i1_0 = __mlir_attr.`0: i1` 40 | -------------------------------------------------------------------------------- /external/gojo/io/io.mojo: -------------------------------------------------------------------------------- 1 | from collections.optional import Optional 2 | from ..builtins import cap, copy, Byte, panic 3 | from .traits import ERR_UNEXPECTED_EOF 4 | 5 | alias BUFFER_SIZE = 4096 6 | 7 | 8 | fn write_string[W: Writer](inout writer: W, string: String) -> (Int, Error): 9 | """Writes the contents of the string s to w, which accepts a slice of bytes. 10 | If w implements [StringWriter], [StringWriter.write_string] is invoked directly. 11 | Otherwise, [Writer.write] is called exactly once. 12 | 13 | Args: 14 | writer: The writer to write to. 15 | string: The string to write. 16 | 17 | Returns: 18 | The number of bytes written and an error, if any. 19 | """ 20 | return writer.write(string.as_bytes()) 21 | 22 | 23 | fn write_string[W: StringWriter](inout writer: W, string: String) -> (Int, Error): 24 | """Writes the contents of the string s to w, which accepts a slice of bytes. 25 | If w implements [StringWriter], [StringWriter.write_string] is invoked directly. 26 | Otherwise, [Writer.write] is called exactly once. 27 | 28 | Args: 29 | writer: The writer to write to. 30 | string: The string to write. 31 | 32 | Returns: 33 | The number of bytes written and an error, if any.""" 34 | return writer.write_string(string) 35 | 36 | 37 | fn read_at_least[R: Reader](inout reader: R, inout dest: List[Byte], min: Int) -> (Int, Error): 38 | """Reads from r into buf until it has read at least min bytes. 39 | It returns the number of bytes copied and an error if fewer bytes were read. 40 | The error is EOF only if no bytes were read. 41 | If an EOF happens after reading fewer than min bytes, 42 | read_at_least returns [ERR_UNEXPECTED_EOF]. 43 | If min is greater than the length of buf, read_at_least returns [ERR_SHORT_BUFFER]. 44 | On return, n >= min if and only if err == nil. 45 | If r returns an error having read at least min bytes, the error is dropped. 46 | 47 | Args: 48 | reader: The reader to read from. 49 | dest: The buffer to read into. 50 | min: The minimum number of bytes to read. 51 | 52 | Returns: 53 | The number of bytes read.""" 54 | var error = Error() 55 | if len(dest) < min: 56 | return 0, Error(io.ERR_SHORT_BUFFER) 57 | 58 | var total_bytes_read: Int = 0 59 | while total_bytes_read < min and not error: 60 | var bytes_read: Int 61 | bytes_read, error = reader.read(dest) 62 | total_bytes_read += bytes_read 63 | 64 | if total_bytes_read >= min: 65 | error = Error() 66 | 67 | elif total_bytes_read > 0 and str(error): 68 | error = Error(ERR_UNEXPECTED_EOF) 69 | 70 | return total_bytes_read, error 71 | 72 | 73 | fn read_full[R: Reader](inout reader: R, inout dest: List[Byte]) -> (Int, Error): 74 | """Reads exactly len(buf) bytes from r into buf. 75 | It returns the number of bytes copied and an error if fewer bytes were read. 76 | The error is EOF only if no bytes were read. 77 | If an EOF happens after reading some but not all the bytes, 78 | read_full returns [ERR_UNEXPECTED_EOF]. 79 | On return, n == len(buf) if and only if err == nil. 80 | If r returns an error having read at least len(buf) bytes, the error is dropped. 81 | """ 82 | return read_at_least(reader, dest, len(dest)) 83 | 84 | 85 | # fn copy_n[W: Writer, R: Reader](dst: W, src: R, n: Int64) raises -> Int64: 86 | # """Copies n bytes (or until an error) from src to dst. 87 | # It returns the number of bytes copied and the earliest 88 | # error encountered while copying. 89 | # On return, written == n if and only if err == nil. 90 | 91 | # If dst implements [ReaderFrom], the copy is implemented using it. 92 | # """ 93 | # var written = copy(dst, LimitReader(src, n)) 94 | # if written == n: 95 | # return n 96 | 97 | # if written < n: 98 | # # src stopped early; must have been EOF. 99 | # raise Error(ERR_UNEXPECTED_EOF) 100 | 101 | # return written 102 | 103 | 104 | # fn copy[W: Writer, R: Reader](dst: W, src: R, n: Int64) -> Int64: 105 | # """copy copies from src to dst until either EOF is reached 106 | # on src or an error occurs. It returns the number of bytes 107 | # copied and the first error encountered while copying, if any. 108 | 109 | # A successful copy returns err == nil, not err == EOF. 110 | # Because copy is defined to read from src until EOF, it does 111 | # not treat an EOF from Read as an error to be reported. 112 | 113 | # If src implements [WriterTo], 114 | # the copy is implemented by calling src.WriteTo(dst). 115 | # Otherwise, if dst implements [ReaderFrom], 116 | # the copy is implemented by calling dst.ReadFrom(src). 117 | # """ 118 | # return copy_buffer(dst, src, nil) 119 | 120 | # # CopyBuffer is identical to copy except that it stages through the 121 | # # provided buffer (if one is required) rather than allocating a 122 | # # temporary one. If buf is nil, one is allocated; otherwise if it has 123 | # # zero length, CopyBuffer panics. 124 | # # 125 | # # If either src implements [WriterTo] or dst implements [ReaderFrom], 126 | # # buf will not be used to perform the copy. 127 | # fn CopyBuffer(dst Writer, src Reader, buf bytes) (written int64, err error) { 128 | # if buf != nil and len(buf) == 0 { 129 | # panic("empty buffer in CopyBuffer") 130 | # } 131 | # return copy_buffer(dst, src, buf) 132 | # } 133 | 134 | 135 | # fn copy_buffer[W: Writer, R: Reader](dst: W, src: R, buf: Span[Byte]) raises -> Int64: 136 | # """Actual implementation of copy and CopyBuffer. 137 | # if buf is nil, one is allocated. 138 | # """ 139 | # var nr: Int 140 | # nr = src.read(buf) 141 | # while True: 142 | # if nr > 0: 143 | # var nw: Int 144 | # nw = dst.write(get_slice(buf, 0, nr)) 145 | # if nw < 0 or nr < nw: 146 | # nw = 0 147 | 148 | # var written = Int64(nw) 149 | # if nr != nw: 150 | # raise Error(ERR_SHORT_WRITE) 151 | 152 | # return written 153 | 154 | 155 | # fn copy_buffer[W: Writer, R: ReaderWriteTo](dst: W, src: R, buf: Span[Byte]) -> Int64: 156 | # return src.write_to(dst) 157 | 158 | 159 | # fn copy_buffer[W: WriterReadFrom, R: Reader](dst: W, src: R, buf: Span[Byte]) -> Int64: 160 | # return dst.read_from(src) 161 | 162 | # # LimitReader returns a Reader that reads from r 163 | # # but stops with EOF after n bytes. 164 | # # The underlying implementation is a *LimitedReader. 165 | # fn LimitReader(r Reader, n int64) Reader { return &LimitedReader{r, n} } 166 | 167 | # # A LimitedReader reads from R but limits the amount of 168 | # # data returned to just N bytes. Each call to Read 169 | # # updates N to reflect the new amount remaining. 170 | # # Read returns EOF when N <= 0 or when the underlying R returns EOF. 171 | # struct LimitedReader(): 172 | # var R: Reader # underlying reader 173 | # N int64 # max bytes remaining 174 | 175 | # fn (l *LimitedReader) Read(p bytes) (n Int, err error) { 176 | # if l.N <= 0 { 177 | # return 0, EOF 178 | # } 179 | # if int64(len(p)) > l.N { 180 | # p = p[0:l.N] 181 | # } 182 | # n, err = l.R.Read(p) 183 | # l.N -= int64(n) 184 | # return 185 | # } 186 | 187 | # # NewSectionReader returns a [SectionReader] that reads from r 188 | # # starting at offset off and stops with EOF after n bytes. 189 | # fn NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader { 190 | # var remaining int64 191 | # const maxint64 = 1<<63 - 1 192 | # if off <= maxint64-n { 193 | # remaining = n + off 194 | # } else { 195 | # # Overflow, with no way to return error. 196 | # # Assume we can read up to an offset of 1<<63 - 1. 197 | # remaining = maxint64 198 | # } 199 | # return &SectionReader{r, off, off, remaining, n} 200 | # } 201 | 202 | # # SectionReader implements Read, Seek, and ReadAt on a section 203 | # # of an underlying [ReaderAt]. 204 | # type SectionReader struct { 205 | # r ReaderAt # constant after creation 206 | # base int64 # constant after creation 207 | # off int64 208 | # limit int64 # constant after creation 209 | # n int64 # constant after creation 210 | # } 211 | 212 | # fn (s *SectionReader) Read(p bytes) (n Int, err error) { 213 | # if s.off >= s.limit { 214 | # return 0, EOF 215 | # } 216 | # if max := s.limit - s.off; int64(len(p)) > max { 217 | # p = p[0:max] 218 | # } 219 | # n, err = s.r.ReadAt(p, s.off) 220 | # s.off += int64(n) 221 | # return 222 | # } 223 | 224 | # alias errWhence = "Seek: invalid whence" 225 | # alias errOffset = "Seek: invalid offset" 226 | 227 | # fn (s *SectionReader) Seek(offset int64, whence Int) (int64, error) { 228 | # switch whence { 229 | # default: 230 | # return 0, errWhence 231 | # case SEEK_START: 232 | # offset += s.base 233 | # case SEEK_CURRENT: 234 | # offset += s.off 235 | # case SEEK_END: 236 | # offset += s.limit 237 | # } 238 | # if offset < s.base { 239 | # return 0, errOffset 240 | # } 241 | # s.off = offset 242 | # return offset - s.base, nil 243 | # } 244 | 245 | # fn (s *SectionReader) ReadAt(p bytes, off int64) (n Int, err error) { 246 | # if off < 0 or off >= s.capacity { 247 | # return 0, EOF 248 | # } 249 | # off += s.base 250 | # if max := s.limit - off; int64(len(p)) > max { 251 | # p = p[0:max] 252 | # n, err = s.r.ReadAt(p, off) 253 | # if err == nil { 254 | # err = EOF 255 | # } 256 | # return n, err 257 | # } 258 | # return s.r.ReadAt(p, off) 259 | # } 260 | 261 | # # Size returns the size of the section in bytes. 262 | # fn (s *SectionReader) Size() int64 { return s.limit - s.base } 263 | 264 | # # Outer returns the underlying [ReaderAt] and offsets for the section. 265 | # # 266 | # # The returned values are the same that were passed to [NewSectionReader] 267 | # # when the [SectionReader] was created. 268 | # fn (s *SectionReader) Outer() (r ReaderAt, off int64, n int64) { 269 | # return s.r, s.base, s.n 270 | # } 271 | 272 | # # An OffsetWriter maps writes at offset base to offset base+off in the underlying writer. 273 | # type OffsetWriter struct { 274 | # w WriterAt 275 | # base int64 # the original offset 276 | # off int64 # the current offset 277 | # } 278 | 279 | # # NewOffsetWriter returns an [OffsetWriter] that writes to w 280 | # # starting at offset off. 281 | # fn NewOffsetWriter(w WriterAt, off int64) *OffsetWriter { 282 | # return &OffsetWriter{w, off, off} 283 | # } 284 | 285 | # fn (o *OffsetWriter) Write(p bytes) (n Int, err error) { 286 | # n, err = o.w.WriteAt(p, o.off) 287 | # o.off += int64(n) 288 | # return 289 | # } 290 | 291 | # fn (o *OffsetWriter) WriteAt(p bytes, off int64) (n Int, err error) { 292 | # if off < 0 { 293 | # return 0, errOffset 294 | # } 295 | 296 | # off += o.base 297 | # return o.w.WriteAt(p, off) 298 | # } 299 | 300 | # fn (o *OffsetWriter) Seek(offset int64, whence Int) (int64, error) { 301 | # switch whence { 302 | # default: 303 | # return 0, errWhence 304 | # case SEEK_START: 305 | # offset += o.base 306 | # case SEEK_CURRENT: 307 | # offset += o.off 308 | # } 309 | # if offset < o.base { 310 | # return 0, errOffset 311 | # } 312 | # o.off = offset 313 | # return offset - o.base, nil 314 | # } 315 | 316 | # # TeeReader returns a [Reader] that writes to w what it reads from r. 317 | # # All reads from r performed through it are matched with 318 | # # corresponding writes to w. There is no internal buffering - 319 | # # the write must complete before the read completes. 320 | # # Any error encountered while writing is reported as a read error. 321 | # fn TeeReader(r Reader, w Writer) Reader { 322 | # return &teeReader{r, w} 323 | # } 324 | 325 | # type teeReader struct { 326 | # r Reader 327 | # w Writer 328 | # } 329 | 330 | # fn (t *teeReader) Read(p bytes) (n Int, err error) { 331 | # n, err = t.r.Read(p) 332 | # if n > 0 { 333 | # if n, err := t.w.Write(p[:n]); err != nil { 334 | # return n, err 335 | # } 336 | # } 337 | # return 338 | # } 339 | 340 | # # Discard is a [Writer] on which all Write calls succeed 341 | # # without doing anything. 342 | # var Discard Writer = discard{} 343 | 344 | # type discard struct{} 345 | 346 | # # discard implements ReaderFrom as an optimization so copy to 347 | # # io.Discard can avoid doing unnecessary work. 348 | # var _ ReaderFrom = discard{} 349 | 350 | # fn (discard) Write(p bytes) (Int, error) { 351 | # return len(p), nil 352 | # } 353 | 354 | # fn (discard) write_string(s string) (Int, error) { 355 | # return len(s), nil 356 | # } 357 | 358 | # var blackHolePool = sync.Pool{ 359 | # New: fn() any { 360 | # b := make(bytes, 8192) 361 | # return &b 362 | # }, 363 | # } 364 | 365 | # fn (discard) ReadFrom(r Reader) (n int64, err error) { 366 | # bufp := blackHolePool.Get().(*bytes) 367 | # readSize := 0 368 | # for { 369 | # readSize, err = r.Read(*bufp) 370 | # n += int64(readSize) 371 | # if err != nil { 372 | # blackHolePool.Put(bufp) 373 | # if err == EOF { 374 | # return n, nil 375 | # } 376 | # return 377 | # } 378 | # } 379 | # } 380 | 381 | # # NopCloser returns a [ReadCloser] with a no-op Close method wrapping 382 | # # the provided [Reader] r. 383 | # # If r implements [WriterTo], the returned [ReadCloser] will implement [WriterTo] 384 | # # by forwarding calls to r. 385 | # fn NopCloser(r Reader) ReadCloser { 386 | # if _, ok := r.(WriterTo); ok { 387 | # return nopCloserWriterTo{r} 388 | # } 389 | # return nopCloser{r} 390 | # } 391 | 392 | # type nopCloser struct { 393 | # Reader 394 | # } 395 | 396 | # fn (nopCloser) Close() error { return nil } 397 | 398 | # type nopCloserWriterTo struct { 399 | # Reader 400 | # } 401 | 402 | # fn (nopCloserWriterTo) Close() error { return nil } 403 | 404 | # fn (c nopCloserWriterTo) WriteTo(w Writer) (n int64, err error) { 405 | # return c.Reader.(WriterTo).WriteTo(w) 406 | # } 407 | 408 | 409 | fn read_all[R: Reader](inout reader: R) -> (List[Byte], Error): 410 | """Reads from r until an error or EOF and returns the data it read. 411 | A successful call returns err == nil, not err == EOF. Because ReadAll is 412 | defined to read from src until EOF, it does not treat an EOF from Read 413 | as an error to be reported. 414 | 415 | Args: 416 | reader: The reader to read from. 417 | 418 | Returns: 419 | The data read.""" 420 | var dest = List[Byte](capacity=BUFFER_SIZE) 421 | var at_eof: Bool = False 422 | 423 | while True: 424 | var temp = List[Byte](capacity=BUFFER_SIZE) 425 | var bytes_read: Int 426 | var err: Error 427 | bytes_read, err = reader.read(temp) 428 | var err_message = str(err) 429 | if err_message != "": 430 | if err_message != EOF: 431 | return dest, err 432 | 433 | at_eof = True 434 | 435 | # If new bytes will overflow the result, resize it. 436 | # if some bytes were written, how do I append before returning result on the last one? 437 | if len(dest) + len(temp) > dest.capacity: 438 | dest.reserve(dest.capacity * 2) 439 | dest.extend(temp) 440 | 441 | if at_eof: 442 | return dest, err 443 | -------------------------------------------------------------------------------- /external/gojo/io/traits.mojo: -------------------------------------------------------------------------------- 1 | from collections.optional import Optional 2 | from ..builtins import Byte 3 | 4 | alias Rune = Int32 5 | 6 | # Package io provides basic interfaces to I/O primitives. 7 | # Its primary job is to wrap existing implementations of such primitives, 8 | # such as those in package os, into shared public interfaces that 9 | # abstract the fntionality, plus some other related primitives. 10 | # 11 | # Because these interfaces and primitives wrap lower-level operations with 12 | # various implementations, unless otherwise informed clients should not 13 | # assume they are safe for parallel execution. 14 | # Seek whence values. 15 | alias SEEK_START = 0 # seek relative to the origin of the file 16 | alias SEEK_CURRENT = 1 # seek relative to the current offset 17 | alias SEEK_END = 2 # seek relative to the end 18 | 19 | # ERR_SHORT_WRITE means that a write accepted fewer bytes than requested 20 | # but failed to return an explicit error. 21 | alias ERR_SHORT_WRITE = "short write" 22 | 23 | # ERR_INVALID_WRITE means that a write returned an impossible count. 24 | alias ERR_INVALID_WRITE = "invalid write result" 25 | 26 | # ERR_SHORT_BUFFER means that a read required a longer buffer than was provided. 27 | alias ERR_SHORT_BUFFER = "short buffer" 28 | 29 | # EOF is the error returned by Read when no more input is available. 30 | # (Read must return EOF itself, not an error wrapping EOF, 31 | # because callers will test for EOF using ==.) 32 | # fntions should return EOF only to signal a graceful end of input. 33 | # If the EOF occurs unexpectedly in a structured data stream, 34 | # the appropriate error is either [ERR_UNEXPECTED_EOF] or some other error 35 | # giving more detail. 36 | alias EOF = "EOF" 37 | 38 | # ERR_UNEXPECTED_EOF means that EOF was encountered in the 39 | # middle of reading a fixed-size block or data structure. 40 | alias ERR_UNEXPECTED_EOF = "unexpected EOF" 41 | 42 | # ERR_NO_PROGRESS is returned by some clients of a [Reader] when 43 | # many calls to Read have failed to return any data or error, 44 | # usually the sign of a broken [Reader] implementation. 45 | alias ERR_NO_PROGRESS = "multiple Read calls return no data or error" 46 | 47 | 48 | trait Reader(Movable): 49 | """Reader is the trait that wraps the basic Read method. 50 | 51 | Read reads up to len(p) bytes into p. It returns the number of bytes 52 | read (0 <= n <= len(p)) and any error encountered. Even if Read 53 | returns n < len(p), it may use all of p as scratch space during the call. 54 | If some data is available but not len(p) bytes, Read conventionally 55 | returns what is available instead of waiting for more. 56 | 57 | When Read encounters an error or end-of-file condition after 58 | successfully reading n > 0 bytes, it returns the number of 59 | bytes read. It may return the (non-nil) error from the same call 60 | or return the error (and n == 0) from a subsequent call. 61 | An instance of this general case is that a Reader returning 62 | a non-zero number of bytes at the end of the input stream may 63 | return either err == EOF or err == nil. The next Read should 64 | return 0, EOF. 65 | 66 | Callers should always process the n > 0 bytes returned before 67 | considering the error err. Doing so correctly handles I/O errors 68 | that happen after reading some bytes and also both of the 69 | allowed EOF behaviors. 70 | 71 | If len(p) == 0, Read should always return n == 0. It may return a 72 | non-nil error if some error condition is known, such as EOF. 73 | 74 | Implementations of Read are discouraged from returning a 75 | zero byte count with a nil error, except when len(p) == 0. 76 | Callers should treat a return of 0 and nil as indicating that 77 | nothing happened; in particular it does not indicate EOF. 78 | 79 | Implementations must not retain p.""" 80 | 81 | fn read(inout self, inout dest: List[Byte]) -> (Int, Error): 82 | ... 83 | 84 | 85 | trait Writer(Movable): 86 | """Writer is the trait that wraps the basic Write method. 87 | 88 | Write writes len(p) bytes from p to the underlying data stream. 89 | It returns the number of bytes written from p (0 <= n <= len(p)) 90 | and any error encountered that caused the write to stop early. 91 | Write must return a non-nil error if it returns n < len(p). 92 | Write must not modify the slice data, even temporarily. 93 | 94 | Implementations must not retain p. 95 | """ 96 | 97 | fn write(inout self, src: List[Byte]) -> (Int, Error): 98 | ... 99 | 100 | 101 | trait Closer(Movable): 102 | """ 103 | Closer is the trait that wraps the basic Close method. 104 | 105 | The behavior of Close after the first call is undefined. 106 | Specific implementations may document their own behavior. 107 | """ 108 | 109 | fn close(inout self) -> Error: 110 | ... 111 | 112 | 113 | trait Seeker(Movable): 114 | """ 115 | Seeker is the trait that wraps the basic Seek method. 116 | 117 | Seek sets the offset for the next Read or Write to offset, 118 | interpreted according to whence: 119 | [SEEK_START] means relative to the start of the file, 120 | [SEEK_CURRENT] means relative to the current offset, and 121 | [SEEK_END] means relative to the end 122 | (for example, offset = -2 specifies the penultimate byte of the file). 123 | Seek returns the new offset relative to the start of the 124 | file or an error, if any. 125 | 126 | Seeking to an offset before the start of the file is an error. 127 | Seeking to any positive offset may be allowed, but if the new offset exceeds 128 | the size of the underlying object the behavior of subsequent I/O operations 129 | is implementation-dependent. 130 | """ 131 | 132 | fn seek(inout self, offset: Int64, whence: Int) -> (Int64, Error): 133 | ... 134 | 135 | 136 | trait ReadWriter(Reader, Writer): 137 | ... 138 | 139 | 140 | trait ReadCloser(Reader, Closer): 141 | ... 142 | 143 | 144 | trait WriteCloser(Writer, Closer): 145 | ... 146 | 147 | 148 | trait ReadWriteCloser(Reader, Writer, Closer): 149 | ... 150 | 151 | 152 | trait ReadSeeker(Reader, Seeker): 153 | ... 154 | 155 | 156 | trait ReadSeekCloser(Reader, Seeker, Closer): 157 | ... 158 | 159 | 160 | trait WriteSeeker(Writer, Seeker): 161 | ... 162 | 163 | 164 | trait ReadWriteSeeker(Reader, Writer, Seeker): 165 | ... 166 | 167 | 168 | trait ReaderFrom: 169 | """ReaderFrom is the trait that wraps the ReadFrom method. 170 | 171 | ReadFrom reads data from r until EOF or error. 172 | The return value n is the number of bytes read. 173 | Any error except EOF encountered during the read is also returned. 174 | 175 | The [copy] function uses [ReaderFrom] if available.""" 176 | 177 | fn read_from[R: Reader](inout self, inout reader: R) -> (Int64, Error): 178 | ... 179 | 180 | 181 | trait WriterReadFrom(Writer, ReaderFrom): 182 | ... 183 | 184 | 185 | trait WriterTo: 186 | """WriterTo is the trait that wraps the WriteTo method. 187 | 188 | WriteTo writes data to w until there's no more data to write or 189 | when an error occurs. The return value n is the number of bytes 190 | written. Any error encountered during the write is also returned. 191 | 192 | The copy function uses WriterTo if available.""" 193 | 194 | fn write_to[W: Writer](inout self, inout writer: W) -> (Int64, Error): 195 | ... 196 | 197 | 198 | trait ReaderWriteTo(Reader, WriterTo): 199 | ... 200 | 201 | 202 | trait ReaderAt: 203 | """ReaderAt is the trait that wraps the basic ReadAt method. 204 | 205 | ReadAt reads len(p) bytes into p starting at offset off in the 206 | underlying input source. It returns the number of bytes 207 | read (0 <= n <= len(p)) and any error encountered. 208 | 209 | When ReadAt returns n < len(p), it returns a non-nil error 210 | explaining why more bytes were not returned. In this respect, 211 | ReadAt is stricter than Read. 212 | 213 | Even if ReadAt returns n < len(p), it may use all of p as scratch 214 | space during the call. If some data is available but not len(p) bytes, 215 | ReadAt blocks until either all the data is available or an error occurs. 216 | In this respect ReadAt is different from Read. 217 | 218 | If the n = len(p) bytes returned by ReadAt are at the end of the 219 | input source, ReadAt may return either err == EOF or err == nil. 220 | 221 | If ReadAt is reading from an input source with a seek offset, 222 | ReadAt should not affect nor be affected by the underlying 223 | seek offset. 224 | 225 | Clients of ReadAt can execute parallel ReadAt calls on the 226 | same input source. 227 | 228 | Implementations must not retain p.""" 229 | 230 | fn read_at(self, inout dest: List[Byte], off: Int64) -> (Int, Error): 231 | ... 232 | 233 | 234 | trait WriterAt: 235 | """WriterAt is the trait that wraps the basic WriteAt method. 236 | 237 | WriteAt writes len(p) bytes from p to the underlying data stream 238 | at offset off. It returns the number of bytes written from p (0 <= n <= len(p)) 239 | and any error encountered that caused the write to stop early. 240 | WriteAt must return a non-nil error if it returns n < len(p). 241 | 242 | If WriteAt is writing to a destination with a seek offset, 243 | WriteAt should not affect nor be affected by the underlying 244 | seek offset. 245 | 246 | Clients of WriteAt can execute parallel WriteAt calls on the same 247 | destination if the ranges do not overlap. 248 | 249 | Implementations must not retain p.""" 250 | 251 | fn write_at(self, src: Span[Byte], off: Int64) -> (Int, Error): 252 | ... 253 | 254 | 255 | trait ByteReader: 256 | """ByteReader is the trait that wraps the read_byte method. 257 | 258 | read_byte reads and returns the next byte from the input or 259 | any error encountered. If read_byte returns an error, no input 260 | byte was consumed, and the returned byte value is undefined. 261 | 262 | read_byte provides an efficient trait for byte-at-time 263 | processing. A [Reader] that does not implement ByteReader 264 | can be wrapped using bufio.NewReader to add this method.""" 265 | 266 | fn read_byte(inout self) -> (Byte, Error): 267 | ... 268 | 269 | 270 | trait ByteScanner(ByteReader): 271 | """ByteScanner is the trait that adds the unread_byte method to the 272 | basic read_byte method. 273 | 274 | unread_byte causes the next call to read_byte to return the last byte read. 275 | If the last operation was not a successful call to read_byte, unread_byte may 276 | return an error, unread the last byte read (or the byte prior to the 277 | last-unread byte), or (in implementations that support the [Seeker] trait) 278 | seek to one byte before the current offset.""" 279 | 280 | fn unread_byte(inout self) -> Error: 281 | ... 282 | 283 | 284 | trait ByteWriter: 285 | """ByteWriter is the trait that wraps the write_byte method.""" 286 | 287 | fn write_byte(inout self, byte: Byte) -> (Int, Error): 288 | ... 289 | 290 | 291 | trait RuneReader: 292 | """RuneReader is the trait that wraps the read_rune method. 293 | 294 | read_rune reads a single encoded Unicode character 295 | and returns the rune and its size in bytes. If no character is 296 | available, err will be set.""" 297 | 298 | fn read_rune(inout self) -> (Rune, Int): 299 | ... 300 | 301 | 302 | trait RuneScanner(RuneReader): 303 | """RuneScanner is the trait that adds the unread_rune method to the 304 | basic read_rune method. 305 | 306 | unread_rune causes the next call to read_rune to return the last rune read. 307 | If the last operation was not a successful call to read_rune, unread_rune may 308 | return an error, unread the last rune read (or the rune prior to the 309 | last-unread rune), or (in implementations that support the [Seeker] trait) 310 | seek to the start of the rune before the current offset.""" 311 | 312 | fn unread_rune(inout self) -> Rune: 313 | ... 314 | 315 | 316 | trait StringWriter: 317 | """StringWriter is the trait that wraps the WriteString method.""" 318 | 319 | fn write_string(inout self, src: String) -> (Int, Error): 320 | ... 321 | -------------------------------------------------------------------------------- /external/gojo/net/__init__.mojo: -------------------------------------------------------------------------------- 1 | """Adapted from go's net package 2 | 3 | A good chunk of the leg work here came from the lightbug_http project! https://github.com/saviorand/lightbug_http/tree/main 4 | """ 5 | 6 | from .fd import FileDescriptor 7 | from .socket import Socket 8 | from .tcp import TCPConnection, TCPListener, listen_tcp 9 | from .address import TCPAddr, NetworkType, Addr 10 | from .ip import get_ip_address, get_addr_info 11 | from .dial import dial_tcp, Dialer 12 | from .net import Connection, Conn 13 | -------------------------------------------------------------------------------- /external/gojo/net/address.mojo: -------------------------------------------------------------------------------- 1 | @value 2 | struct NetworkType: 3 | var value: String 4 | 5 | alias empty = NetworkType("") 6 | alias tcp = NetworkType("tcp") 7 | alias tcp4 = NetworkType("tcp4") 8 | alias tcp6 = NetworkType("tcp6") 9 | alias udp = NetworkType("udp") 10 | alias udp4 = NetworkType("udp4") 11 | alias udp6 = NetworkType("udp6") 12 | alias ip = NetworkType("ip") 13 | alias ip4 = NetworkType("ip4") 14 | alias ip6 = NetworkType("ip6") 15 | alias unix = NetworkType("unix") 16 | 17 | 18 | trait Addr(CollectionElement, Stringable): 19 | fn network(self) -> String: 20 | """Name of the network (for example, "tcp", "udp").""" 21 | ... 22 | 23 | 24 | @value 25 | struct TCPAddr(Addr): 26 | """Addr struct representing a TCP address. 27 | 28 | Args: 29 | ip: IP address. 30 | port: Port number. 31 | zone: IPv6 addressing zone. 32 | """ 33 | 34 | var ip: String 35 | var port: Int 36 | var zone: String # IPv6 addressing zone 37 | 38 | fn __init__(inout self): 39 | self.ip = String("127.0.0.1") 40 | self.port = 8000 41 | self.zone = "" 42 | 43 | fn __init__(inout self, ip: String, port: Int): 44 | self.ip = ip 45 | self.port = port 46 | self.zone = "" 47 | 48 | fn __str__(self) -> String: 49 | if self.zone != "": 50 | return join_host_port(str(self.ip) + "%" + self.zone, str(self.port)) 51 | return join_host_port(self.ip, str(self.port)) 52 | 53 | fn network(self) -> String: 54 | return NetworkType.tcp.value 55 | 56 | 57 | fn resolve_internet_addr(network: String, address: String) raises -> TCPAddr: 58 | var host: String = "" 59 | var port: String = "" 60 | var portnum: Int = 0 61 | if ( 62 | network == NetworkType.tcp.value 63 | or network == NetworkType.tcp4.value 64 | or network == NetworkType.tcp6.value 65 | or network == NetworkType.udp.value 66 | or network == NetworkType.udp4.value 67 | or network == NetworkType.udp6.value 68 | ): 69 | if address != "": 70 | var host_port = split_host_port(address) 71 | host = host_port.host 72 | port = str(host_port.port) 73 | portnum = atol(port.__str__()) 74 | elif network == NetworkType.ip.value or network == NetworkType.ip4.value or network == NetworkType.ip6.value: 75 | if address != "": 76 | host = address 77 | elif network == NetworkType.unix.value: 78 | raise Error("Unix addresses not supported yet") 79 | else: 80 | raise Error("unsupported network type: " + network) 81 | return TCPAddr(host, portnum) 82 | 83 | 84 | alias missingPortError = Error("missing port in address") 85 | alias tooManyColonsError = Error("too many colons in address") 86 | 87 | 88 | struct HostPort(Stringable): 89 | var host: String 90 | var port: Int 91 | 92 | fn __init__(inout self, host: String, port: Int): 93 | self.host = host 94 | self.port = port 95 | 96 | fn __str__(self) -> String: 97 | return join_host_port(self.host, str(self.port)) 98 | 99 | 100 | fn join_host_port(host: String, port: String) -> String: 101 | if host.find(":") != -1: # must be IPv6 literal 102 | return "[" + host + "]:" + port 103 | return host + ":" + port 104 | 105 | 106 | fn split_host_port(hostport: String) raises -> HostPort: 107 | var host: String = "" 108 | var port: String = "" 109 | var colon_index = hostport.rfind(":") 110 | var j: Int = 0 111 | var k: Int = 0 112 | 113 | if colon_index == -1: 114 | raise missingPortError 115 | if hostport[0] == "[": 116 | var end_bracket_index = hostport.find("]") 117 | if end_bracket_index == -1: 118 | raise Error("missing ']' in address") 119 | if end_bracket_index + 1 == len(hostport): 120 | raise missingPortError 121 | elif end_bracket_index + 1 == colon_index: 122 | host = hostport[1:end_bracket_index] 123 | j = 1 124 | k = end_bracket_index + 1 125 | else: 126 | if hostport[end_bracket_index + 1] == ":": 127 | raise tooManyColonsError 128 | else: 129 | raise missingPortError 130 | else: 131 | host = hostport[:colon_index] 132 | if host.find(":") != -1: 133 | raise tooManyColonsError 134 | if hostport[j:].find("[") != -1: 135 | raise Error("unexpected '[' in address") 136 | if hostport[k:].find("]") != -1: 137 | raise Error("unexpected ']' in address") 138 | port = hostport[colon_index + 1 :] 139 | 140 | if port == "": 141 | raise missingPortError 142 | if host == "": 143 | raise Error("missing host") 144 | 145 | return HostPort(host, atol(port)) 146 | -------------------------------------------------------------------------------- /external/gojo/net/dial.mojo: -------------------------------------------------------------------------------- 1 | from .tcp import TCPAddr, TCPConnection, resolve_internet_addr 2 | from .socket import Socket 3 | from .address import split_host_port 4 | 5 | 6 | @value 7 | struct Dialer: 8 | var local_address: TCPAddr 9 | 10 | fn dial(self, network: String, address: String) raises -> TCPConnection: 11 | var tcp_addr = resolve_internet_addr(network, address) 12 | var socket = Socket(local_address=self.local_address) 13 | socket.connect(tcp_addr.ip, tcp_addr.port) 14 | return TCPConnection(socket^) 15 | 16 | 17 | fn dial_tcp(network: String, remote_address: TCPAddr) raises -> TCPConnection: 18 | """Connects to the address on the named network. 19 | 20 | The network must be "tcp", "tcp4", or "tcp6". 21 | Args: 22 | network: The network type. 23 | remote_address: The remote address to connect to. 24 | 25 | Returns: 26 | The TCP connection. 27 | """ 28 | # TODO: Add conversion of domain name to ip address 29 | return Dialer(remote_address).dial(network, remote_address.ip + ":" + str(remote_address.port)) 30 | 31 | 32 | fn dial_tcp(network: String, remote_address: String) raises -> TCPConnection: 33 | """Connects to the address on the named network. 34 | 35 | The network must be "tcp", "tcp4", or "tcp6". 36 | Args: 37 | network: The network type. 38 | remote_address: The remote address to connect to. 39 | 40 | Returns: 41 | The TCP connection. 42 | """ 43 | var address = split_host_port(remote_address) 44 | return Dialer(TCPAddr(address.host, address.port)).dial(network, remote_address) 45 | -------------------------------------------------------------------------------- /external/gojo/net/fd.mojo: -------------------------------------------------------------------------------- 1 | import ..io 2 | from ..builtins import Byte 3 | from ..syscall import ( 4 | recv, 5 | send, 6 | close, 7 | strlen, 8 | FileDescriptorBase, 9 | ) 10 | 11 | alias O_RDWR = 0o2 12 | 13 | 14 | struct FileDescriptor(FileDescriptorBase): 15 | var fd: Int 16 | var is_closed: Bool 17 | 18 | # This takes ownership of a POSIX file descriptor. 19 | fn __moveinit__(inout self, owned existing: Self): 20 | self.fd = existing.fd 21 | self.is_closed = existing.is_closed 22 | 23 | fn __init__(inout self, fd: Int): 24 | self.fd = fd 25 | self.is_closed = False 26 | 27 | fn __del__(owned self): 28 | if not self.is_closed: 29 | var err = self.close() 30 | if err: 31 | print(str(err)) 32 | 33 | fn close(inout self) -> Error: 34 | """Mark the file descriptor as closed.""" 35 | var close_status = close(self.fd) 36 | if close_status == -1: 37 | return Error("FileDescriptor.close: Failed to close socket") 38 | 39 | self.is_closed = True 40 | return Error() 41 | 42 | fn dup(self) -> Self: 43 | """Duplicate the file descriptor.""" 44 | var new_fd = external_call["dup", Int, Int](self.fd) 45 | return Self(new_fd) 46 | 47 | fn read(inout self, inout dest: List[Byte]) -> (Int, Error): 48 | """Receive data from the file descriptor and write it to the buffer provided.""" 49 | var bytes_received = recv( 50 | self.fd, 51 | DTypePointer[DType.uint8](dest.unsafe_ptr()).offset(dest.size), 52 | dest.capacity, 53 | 0, 54 | ) 55 | if bytes_received == -1: 56 | return 0, Error("Failed to receive message from socket.") 57 | dest.size += bytes_received 58 | 59 | if bytes_received < dest.capacity: 60 | return bytes_received, Error(io.EOF) 61 | 62 | return bytes_received, Error() 63 | 64 | fn write(inout self, src: List[Byte]) -> (Int, Error): 65 | """Write data from the buffer to the file descriptor.""" 66 | var bytes_sent = send(self.fd, src.unsafe_ptr(), strlen(src.unsafe_ptr()), 0) 67 | if bytes_sent == -1: 68 | return 0, Error("Failed to send message") 69 | 70 | return bytes_sent, Error() 71 | -------------------------------------------------------------------------------- /external/gojo/net/ip.mojo: -------------------------------------------------------------------------------- 1 | from utils.variant import Variant 2 | from utils.static_tuple import StaticTuple 3 | from sys.info import os_is_linux, os_is_macos 4 | from ..syscall import ( 5 | c_int, 6 | c_char, 7 | c_void, 8 | c_uint, 9 | addrinfo, 10 | addrinfo_unix, 11 | AddressFamily, 12 | AddressInformation, 13 | SocketOptions, 14 | SocketType, 15 | ProtocolFamily, 16 | sockaddr, 17 | sockaddr_in, 18 | htons, 19 | ntohs, 20 | inet_pton, 21 | inet_ntop, 22 | getaddrinfo, 23 | getaddrinfo_unix, 24 | gai_strerror, 25 | to_char_ptr, 26 | c_charptr_to_string, 27 | ) 28 | 29 | alias AddrInfo = Variant[addrinfo, addrinfo_unix] 30 | 31 | 32 | fn get_addr_info(host: String) raises -> AddrInfo: 33 | if os_is_macos(): 34 | var servinfo = UnsafePointer[addrinfo]().alloc(1) 35 | servinfo[0] = addrinfo() 36 | var hints = addrinfo() 37 | hints.ai_family = AddressFamily.AF_INET 38 | hints.ai_socktype = SocketType.SOCK_STREAM 39 | hints.ai_flags = AddressInformation.AI_PASSIVE 40 | 41 | var host_ptr = to_char_ptr(host) 42 | 43 | var status = getaddrinfo( 44 | host_ptr, 45 | DTypePointer[DType.uint8](), 46 | UnsafePointer.address_of(hints), 47 | UnsafePointer.address_of(servinfo), 48 | ) 49 | if status != 0: 50 | print("getaddrinfo failed to execute with status:", status) 51 | 52 | if not servinfo: 53 | print("servinfo is null") 54 | raise Error("Failed to get address info. Pointer to addrinfo is null.") 55 | 56 | return move_from_pointee(servinfo) 57 | elif os_is_linux(): 58 | var servinfo = UnsafePointer[addrinfo_unix]().alloc(1) 59 | servinfo[0] = addrinfo_unix() 60 | var hints = addrinfo_unix() 61 | hints.ai_family = AddressFamily.AF_INET 62 | hints.ai_socktype = SocketType.SOCK_STREAM 63 | hints.ai_flags = AddressInformation.AI_PASSIVE 64 | 65 | var host_ptr = to_char_ptr(host) 66 | 67 | var status = getaddrinfo_unix( 68 | host_ptr, 69 | DTypePointer[DType.uint8](), 70 | UnsafePointer.address_of(hints), 71 | UnsafePointer.address_of(servinfo), 72 | ) 73 | if status != 0: 74 | print("getaddrinfo failed to execute with status:", status) 75 | 76 | if not servinfo: 77 | print("servinfo is null") 78 | raise Error("Failed to get address info. Pointer to addrinfo is null.") 79 | 80 | return move_from_pointee(servinfo) 81 | else: 82 | raise Error("Windows is not supported yet! Sorry!") 83 | 84 | 85 | fn get_ip_address(host: String) raises -> String: 86 | """Get the IP address of a host.""" 87 | # Call getaddrinfo to get the IP address of the host. 88 | var result = get_addr_info(host) 89 | var ai_addr: UnsafePointer[sockaddr] 90 | var address_family: Int32 = 0 91 | var address_length: UInt32 = 0 92 | if result.isa[addrinfo](): 93 | var addrinfo = result[addrinfo] 94 | ai_addr = addrinfo.ai_addr 95 | address_family = addrinfo.ai_family 96 | address_length = addrinfo.ai_addrlen 97 | else: 98 | var addrinfo = result[addrinfo_unix] 99 | ai_addr = addrinfo.ai_addr 100 | address_family = addrinfo.ai_family 101 | address_length = addrinfo.ai_addrlen 102 | 103 | if not ai_addr: 104 | print("ai_addr is null") 105 | raise Error("Failed to get IP address. getaddrinfo was called successfully, but ai_addr is null.") 106 | 107 | # Cast sockaddr struct to sockaddr_in struct and convert the binary IP to a string using inet_ntop. 108 | var addr_in = move_from_pointee(ai_addr.bitcast[sockaddr_in]()) 109 | 110 | return convert_binary_ip_to_string(addr_in.sin_addr.s_addr, address_family, address_length).strip() 111 | 112 | 113 | fn convert_port_to_binary(port: Int) -> UInt16: 114 | return htons(UInt16(port)) 115 | 116 | 117 | fn convert_binary_port_to_int(port: UInt16) -> Int: 118 | return int(ntohs(port)) 119 | 120 | 121 | fn convert_ip_to_binary(ip_address: String, address_family: Int) -> UInt32: 122 | var ip_buffer = Pointer[c_void].alloc(4) 123 | var status = inet_pton(address_family, to_char_ptr(ip_address), ip_buffer) 124 | if status == -1: 125 | print("Failed to convert IP address to binary") 126 | 127 | return ip_buffer.bitcast[c_uint]().load() 128 | 129 | 130 | fn convert_binary_ip_to_string(owned ip_address: UInt32, address_family: Int32, address_length: UInt32) -> String: 131 | """Convert a binary IP address to a string by calling inet_ntop. 132 | 133 | Args: 134 | ip_address: The binary IP address. 135 | address_family: The address family of the IP address. 136 | address_length: The length of the address. 137 | 138 | Returns: 139 | The IP address as a string. 140 | """ 141 | # It seems like the len of the buffer depends on the length of the string IP. 142 | # Allocating 10 works for localhost (127.0.0.1) which I suspect is 9 bytes + 1 null terminator byte. So max should be 16 (15 + 1). 143 | var ip_buffer = Pointer[c_void].alloc(16) 144 | var ip_address_ptr = Pointer.address_of(ip_address).bitcast[c_void]() 145 | _ = inet_ntop(address_family, ip_address_ptr, ip_buffer, 16) 146 | 147 | var string_buf = ip_buffer.bitcast[Int8]() 148 | var index = 0 149 | while True: 150 | if string_buf[index] == 0: 151 | break 152 | index += 1 153 | 154 | return StringRef(string_buf, index) 155 | 156 | 157 | fn build_sockaddr_pointer(ip_address: String, port: Int, address_family: Int) -> UnsafePointer[sockaddr]: 158 | """Build a sockaddr pointer from an IP address and port number. 159 | https://learn.microsoft.com/en-us/windows/win32/winsock/sockaddr-2 160 | https://learn.microsoft.com/en-us/windows/win32/api/ws2def/ns-ws2def-sockaddr_in. 161 | """ 162 | var bin_port = convert_port_to_binary(port) 163 | var bin_ip = convert_ip_to_binary(ip_address, address_family) 164 | 165 | var ai = sockaddr_in(address_family, bin_port, bin_ip, StaticTuple[c_char, 8]()) 166 | return UnsafePointer[sockaddr_in].address_of(ai).bitcast[sockaddr]() 167 | -------------------------------------------------------------------------------- /external/gojo/net/net.mojo: -------------------------------------------------------------------------------- 1 | from memory.arc import Arc 2 | import ..io 3 | from ..builtins import Byte 4 | from .socket import Socket 5 | from .address import Addr, TCPAddr 6 | 7 | alias DEFAULT_BUFFER_SIZE = 4096 8 | 9 | 10 | trait Conn(io.Writer, io.Reader, io.Closer): 11 | fn __init__(inout self, owned socket: Socket): 12 | ... 13 | 14 | """Conn is a generic stream-oriented network connection.""" 15 | 16 | fn local_address(self) -> TCPAddr: 17 | """Returns the local network address, if known.""" 18 | ... 19 | 20 | fn remote_address(self) -> TCPAddr: 21 | """Returns the local network address, if known.""" 22 | ... 23 | 24 | # fn set_deadline(self, t: time.Time) -> Error: 25 | # """Sets the read and write deadlines associated 26 | # with the connection. It is equivalent to calling both 27 | # SetReadDeadline and SetWriteDeadline. 28 | 29 | # A deadline is an absolute time after which I/O operations 30 | # fail instead of blocking. The deadline applies to all future 31 | # and pending I/O, not just the immediately following call to 32 | # read or write. After a deadline has been exceeded, the 33 | # connection can be refreshed by setting a deadline in the future. 34 | 35 | # If the deadline is exceeded a call to read or write or to other 36 | # I/O methods will return an error that wraps os.ErrDeadlineExceeded. 37 | # This can be tested using errors.Is(err, os.ErrDeadlineExceeded). 38 | # The error's Timeout method will return true, but note that there 39 | # are other possible errors for which the Timeout method will 40 | # return true even if the deadline has not been exceeded. 41 | 42 | # An idle timeout can be implemented by repeatedly extending 43 | # the deadline after successful read or write calls. 44 | 45 | # A zero value for t means I/O operations will not time out.""" 46 | # ... 47 | 48 | # fn set_read_deadline(self, t: time.Time) -> Error: 49 | # """Sets the deadline for future read calls 50 | # and any currently-blocked read call. 51 | # A zero value for t means read will not time out.""" 52 | # ... 53 | 54 | # fn set_write_deadline(self, t: time.Time) -> Error: 55 | # """Sets the deadline for future write calls 56 | # and any currently-blocked write call. 57 | # Even if write times out, it may return n > 0, indicating that 58 | # some of the data was successfully written. 59 | # A zero value for t means write will not time out.""" 60 | # ... 61 | 62 | 63 | struct Connection(Conn): 64 | """Connection is a concrete generic stream-oriented network connection. 65 | It is used as the internal connection for structs like TCPConnection. 66 | 67 | Args: 68 | fd: The file descriptor of the connection. 69 | """ 70 | 71 | var fd: Socket 72 | 73 | fn __init__(inout self, owned socket: Socket): 74 | self.fd = socket^ 75 | 76 | fn __moveinit__(inout self, owned existing: Self): 77 | self.fd = existing.fd^ 78 | 79 | fn read(inout self, inout dest: List[Byte]) -> (Int, Error): 80 | """Reads data from the underlying file descriptor. 81 | 82 | Args: 83 | dest: The buffer to read data into. 84 | 85 | Returns: 86 | The number of bytes read, or an error if one occurred. 87 | """ 88 | return self.fd.read(dest) 89 | 90 | fn write(inout self, src: List[Byte]) -> (Int, Error): 91 | """Writes data to the underlying file descriptor. 92 | 93 | Args: 94 | src: The buffer to read data into. 95 | 96 | Returns: 97 | The number of bytes written, or an error if one occurred. 98 | """ 99 | return self.fd.write(src) 100 | 101 | fn close(inout self) -> Error: 102 | """Closes the underlying file descriptor. 103 | 104 | Returns: 105 | An error if one occurred, or None if the file descriptor was closed successfully. 106 | """ 107 | return self.fd.close() 108 | 109 | fn local_address(self) -> TCPAddr: 110 | """Returns the local network address. 111 | The Addr returned is shared by all invocations of local_address, so do not modify it. 112 | """ 113 | return self.fd.local_address 114 | 115 | fn remote_address(self) -> TCPAddr: 116 | """Returns the remote network address. 117 | The Addr returned is shared by all invocations of remote_address, so do not modify it. 118 | """ 119 | return self.fd.remote_address 120 | -------------------------------------------------------------------------------- /external/gojo/net/socket.mojo: -------------------------------------------------------------------------------- 1 | from ..builtins import Byte 2 | from ..syscall.file import close 3 | from ..syscall import ( 4 | c_void, 5 | c_uint, 6 | c_char, 7 | c_int, 8 | ) 9 | from ..syscall import ( 10 | sockaddr, 11 | sockaddr_in, 12 | addrinfo, 13 | addrinfo_unix, 14 | socklen_t, 15 | socket, 16 | connect, 17 | recv, 18 | send, 19 | shutdown, 20 | inet_pton, 21 | inet_ntoa, 22 | inet_ntop, 23 | to_char_ptr, 24 | htons, 25 | ntohs, 26 | strlen, 27 | getaddrinfo, 28 | getaddrinfo_unix, 29 | gai_strerror, 30 | c_charptr_to_string, 31 | bind, 32 | listen, 33 | accept, 34 | setsockopt, 35 | getsockopt, 36 | getsockname, 37 | getpeername, 38 | AddressFamily, 39 | AddressInformation, 40 | SocketOptions, 41 | SocketType, 42 | SHUT_RDWR, 43 | SOL_SOCKET, 44 | ) 45 | from .fd import FileDescriptor, FileDescriptorBase 46 | from .ip import ( 47 | convert_binary_ip_to_string, 48 | build_sockaddr_pointer, 49 | convert_binary_port_to_int, 50 | ) 51 | from .address import Addr, TCPAddr, HostPort 52 | 53 | alias SocketClosedError = Error("Socket: Socket is already closed") 54 | 55 | 56 | struct Socket(FileDescriptorBase): 57 | """Represents a network file descriptor. Wraps around a file descriptor and provides network functions. 58 | 59 | Args: 60 | local_address: The local address of the socket (local address if bound). 61 | remote_address: The remote address of the socket (peer's address if connected). 62 | address_family: The address family of the socket. 63 | socket_type: The socket type. 64 | protocol: The protocol. 65 | """ 66 | 67 | var sockfd: FileDescriptor 68 | var address_family: Int 69 | var socket_type: UInt8 70 | var protocol: UInt8 71 | var local_address: TCPAddr 72 | var remote_address: TCPAddr 73 | var _closed: Bool 74 | var _is_connected: Bool 75 | 76 | fn __init__( 77 | inout self, 78 | local_address: TCPAddr = TCPAddr(), 79 | remote_address: TCPAddr = TCPAddr(), 80 | address_family: Int = AddressFamily.AF_INET, 81 | socket_type: UInt8 = SocketType.SOCK_STREAM, 82 | protocol: UInt8 = 0, 83 | ) raises: 84 | """Create a new socket object. 85 | 86 | Args: 87 | local_address: The local address of the socket (local address if bound). 88 | remote_address: The remote address of the socket (peer's address if connected). 89 | address_family: The address family of the socket. 90 | socket_type: The socket type. 91 | protocol: The protocol. 92 | """ 93 | self.address_family = address_family 94 | self.socket_type = socket_type 95 | self.protocol = protocol 96 | 97 | var fd = socket(address_family, SocketType.SOCK_STREAM, 0) 98 | if fd == -1: 99 | raise Error("Socket creation error") 100 | self.sockfd = FileDescriptor(int(fd)) 101 | self.local_address = local_address 102 | self.remote_address = remote_address 103 | self._closed = False 104 | self._is_connected = False 105 | 106 | fn __init__( 107 | inout self, 108 | fd: Int32, 109 | address_family: Int, 110 | socket_type: UInt8, 111 | protocol: UInt8, 112 | local_address: TCPAddr = TCPAddr(), 113 | remote_address: TCPAddr = TCPAddr(), 114 | ): 115 | """ 116 | Create a new socket object when you already have a socket file descriptor. Typically through socket.accept(). 117 | 118 | Args: 119 | fd: The file descriptor of the socket. 120 | address_family: The address family of the socket. 121 | socket_type: The socket type. 122 | protocol: The protocol. 123 | local_address: Local address of socket. 124 | remote_address: Remote address of port. 125 | """ 126 | self.sockfd = FileDescriptor(int(fd)) 127 | self.address_family = address_family 128 | self.socket_type = socket_type 129 | self.protocol = protocol 130 | self.local_address = local_address 131 | self.remote_address = remote_address 132 | self._closed = False 133 | self._is_connected = True 134 | 135 | fn __moveinit__(inout self, owned existing: Self): 136 | self.sockfd = existing.sockfd^ 137 | self.address_family = existing.address_family 138 | self.socket_type = existing.socket_type 139 | self.protocol = existing.protocol 140 | self.local_address = existing.local_address^ 141 | self.remote_address = existing.remote_address^ 142 | self._closed = existing._closed 143 | self._is_connected = existing._is_connected 144 | 145 | # fn __enter__(owned self) -> Self: 146 | # return self^ 147 | 148 | # fn __exit__(inout self): 149 | # if self._is_connected: 150 | # self.shutdown() 151 | # if not self._closed: 152 | # _ = self.close() 153 | 154 | fn __del__(owned self): 155 | if self._is_connected: 156 | self.shutdown() 157 | if not self._closed: 158 | var err = self.close() 159 | _ = self.sockfd.fd 160 | if err: 161 | print("Failed to close socket during deletion:", str(err)) 162 | 163 | @always_inline 164 | fn accept(self) raises -> Self: 165 | """Accept a connection. The socket must be bound to an address and listening for connections. 166 | The return value is a connection where conn is a new socket object usable to send and receive data on the connection, 167 | and address is the address bound to the socket on the other end of the connection. 168 | """ 169 | var their_addr_ptr = UnsafePointer[sockaddr].alloc(1) 170 | var sin_size = socklen_t(sizeof[socklen_t]()) 171 | var new_sockfd = accept( 172 | self.sockfd.fd, 173 | their_addr_ptr, 174 | UnsafePointer[socklen_t].address_of(sin_size), 175 | ) 176 | if new_sockfd == -1: 177 | raise Error("Failed to accept connection") 178 | 179 | var remote = self.get_peer_name() 180 | return Self( 181 | new_sockfd, 182 | self.address_family, 183 | self.socket_type, 184 | self.protocol, 185 | self.local_address, 186 | TCPAddr(remote.host, remote.port), 187 | ) 188 | 189 | fn listen(self, backlog: Int = 0) raises: 190 | """Enable a server to accept connections. 191 | 192 | Args: 193 | backlog: The maximum number of queued connections. Should be at least 0, and the maximum is system-dependent (usually 5). 194 | """ 195 | var queued = backlog 196 | if backlog < 0: 197 | queued = 0 198 | if listen(self.sockfd.fd, queued) == -1: 199 | raise Error("Failed to listen for connections") 200 | 201 | @always_inline 202 | fn bind(inout self, address: String, port: Int) raises: 203 | """Bind the socket to address. The socket must not already be bound. (The format of address depends on the address family). 204 | 205 | When a socket is created with Socket(), it exists in a name 206 | space (address family) but has no address assigned to it. bind() 207 | assigns the address specified by addr to the socket referred to 208 | by the file descriptor sockfd. addrlen specifies the size, in 209 | bytes, of the address structure pointed to by addr. 210 | Traditionally, this operation is called 'assigning a name to a 211 | socket'. 212 | 213 | Args: 214 | address: String - The IP address to bind the socket to. 215 | port: The port number to bind the socket to. 216 | """ 217 | var sockaddr_pointer = build_sockaddr_pointer(address, port, self.address_family) 218 | 219 | if bind(self.sockfd.fd, sockaddr_pointer, sizeof[sockaddr_in]()) == -1: 220 | _ = shutdown(self.sockfd.fd, SHUT_RDWR) 221 | raise Error("Binding socket failed. Wait a few seconds and try again?") 222 | 223 | var local = self.get_sock_name() 224 | self.local_address = TCPAddr(local.host, local.port) 225 | 226 | @always_inline 227 | fn file_no(self) -> Int32: 228 | """Return the file descriptor of the socket.""" 229 | return self.sockfd.fd 230 | 231 | @always_inline 232 | fn get_sock_name(self) raises -> HostPort: 233 | """Return the address of the socket.""" 234 | if self._closed: 235 | raise SocketClosedError 236 | 237 | # TODO: Add check to see if the socket is bound and error if not. 238 | 239 | var local_address_ptr = UnsafePointer[sockaddr].alloc(1) 240 | var local_address_ptr_size = socklen_t(sizeof[sockaddr]()) 241 | var status = getsockname( 242 | self.sockfd.fd, 243 | local_address_ptr, 244 | UnsafePointer[socklen_t].address_of(local_address_ptr_size), 245 | ) 246 | if status == -1: 247 | raise Error("Socket.get_sock_name: Failed to get address of local socket.") 248 | var addr_in = move_from_pointee(local_address_ptr.bitcast[sockaddr_in]()) 249 | 250 | return HostPort( 251 | host=convert_binary_ip_to_string(addr_in.sin_addr.s_addr, AddressFamily.AF_INET, 16), 252 | port=convert_binary_port_to_int(addr_in.sin_port), 253 | ) 254 | 255 | fn get_peer_name(self) raises -> HostPort: 256 | """Return the address of the peer connected to the socket.""" 257 | if self._closed: 258 | raise SocketClosedError 259 | 260 | # TODO: Add check to see if the socket is bound and error if not. 261 | var remote_address_ptr = UnsafePointer[sockaddr].alloc(1) 262 | var remote_address_ptr_size = socklen_t(sizeof[sockaddr]()) 263 | var status = getpeername( 264 | self.sockfd.fd, 265 | remote_address_ptr, 266 | UnsafePointer[socklen_t].address_of(remote_address_ptr_size), 267 | ) 268 | if status == -1: 269 | raise Error("Socket.get_peer_name: Failed to get address of remote socket.") 270 | 271 | # Cast sockaddr struct to sockaddr_in to convert binary IP to string. 272 | var addr_in = move_from_pointee(remote_address_ptr.bitcast[sockaddr_in]()) 273 | 274 | return HostPort( 275 | host=convert_binary_ip_to_string(addr_in.sin_addr.s_addr, AddressFamily.AF_INET, 16), 276 | port=convert_binary_port_to_int(addr_in.sin_port), 277 | ) 278 | 279 | fn get_socket_option(self, option_name: Int) raises -> Int: 280 | """Return the value of the given socket option. 281 | 282 | Args: 283 | option_name: The socket option to get. 284 | """ 285 | var option_value_pointer = UnsafePointer[c_void].alloc(1) 286 | var option_len = socklen_t(sizeof[socklen_t]()) 287 | var option_len_pointer = UnsafePointer.address_of(option_len) 288 | var status = getsockopt( 289 | self.sockfd.fd, 290 | SOL_SOCKET, 291 | option_name, 292 | option_value_pointer, 293 | option_len_pointer, 294 | ) 295 | if status == -1: 296 | raise Error("Socket.get_sock_opt failed with status: " + str(status)) 297 | 298 | return move_from_pointee(option_value_pointer.bitcast[Int]()) 299 | 300 | fn set_socket_option(self, option_name: Int, owned option_value: UInt8 = 1) raises: 301 | """Return the value of the given socket option. 302 | 303 | Args: 304 | option_name: The socket option to set. 305 | option_value: The value to set the socket option to. 306 | """ 307 | var option_value_pointer = UnsafePointer[c_void].address_of(option_value) 308 | var option_len = sizeof[socklen_t]() 309 | var status = setsockopt( 310 | self.sockfd.fd, 311 | SOL_SOCKET, 312 | option_name, 313 | option_value_pointer, 314 | option_len, 315 | ) 316 | if status == -1: 317 | raise Error("Socket.set_sock_opt failed with status: " + str(status)) 318 | 319 | fn connect(inout self, address: String, port: Int) raises: 320 | """Connect to a remote socket at address. 321 | 322 | Args: 323 | address: String - The IP address to connect to. 324 | port: The port number to connect to. 325 | """ 326 | var sockaddr_pointer = build_sockaddr_pointer(address, port, self.address_family) 327 | 328 | if connect(self.sockfd.fd, sockaddr_pointer, sizeof[sockaddr_in]()) == -1: 329 | self.shutdown() 330 | raise Error("Socket.connect: Failed to connect to the remote socket at: " + address + ":" + str(port)) 331 | 332 | var remote = self.get_peer_name() 333 | self.remote_address = TCPAddr(remote.host, remote.port) 334 | 335 | @always_inline 336 | fn write(inout self: Self, src: List[Byte]) -> (Int, Error): 337 | """Send data to the socket. The socket must be connected to a remote socket. 338 | 339 | Args: 340 | src: The data to send. 341 | 342 | Returns: 343 | The number of bytes sent. 344 | """ 345 | return self.sockfd.write(src) 346 | 347 | fn send_all(self, src: List[Byte], max_attempts: Int = 3) raises: 348 | """Send data to the socket. The socket must be connected to a remote socket. 349 | 350 | Args: 351 | src: The data to send. 352 | max_attempts: The maximum number of attempts to send the data. 353 | """ 354 | var header_pointer = DTypePointer(src.unsafe_ptr()) 355 | var total_bytes_sent = 0 356 | var attempts = 0 357 | 358 | # Try to send all the data in the buffer. If it did not send all the data, keep trying but start from the offset of the last successful send. 359 | while total_bytes_sent < len(src): 360 | if attempts > max_attempts: 361 | raise Error("Failed to send message after " + str(max_attempts) + " attempts.") 362 | 363 | var bytes_sent = send( 364 | self.sockfd.fd, 365 | header_pointer.offset(total_bytes_sent), 366 | strlen(header_pointer.offset(total_bytes_sent)), 367 | 0, 368 | ) 369 | if bytes_sent == -1: 370 | raise Error("Failed to send message, wrote" + String(total_bytes_sent) + "bytes before failing.") 371 | total_bytes_sent += bytes_sent 372 | attempts += 1 373 | 374 | fn send_to(inout self, src: List[Byte], address: String, port: Int) raises -> Int: 375 | """Send data to the a remote address by connecting to the remote socket before sending. 376 | The socket must be not already be connected to a remote socket. 377 | 378 | Args: 379 | src: The data to send. 380 | address: The IP address to connect to. 381 | port: The port number to connect to. 382 | """ 383 | self.connect(address, port) 384 | var bytes_written: Int 385 | var err: Error 386 | bytes_written, err = self.write(Span(src)) 387 | if err: 388 | raise err 389 | return bytes_written 390 | 391 | @always_inline 392 | fn read(inout self, inout dest: List[Byte]) -> (Int, Error): 393 | """Receive data from the socket.""" 394 | var bytes_read: Int 395 | var err = Error() 396 | bytes_read, err = self.sockfd.read(dest) 397 | 398 | return bytes_read, err 399 | 400 | fn shutdown(self): 401 | _ = shutdown(self.sockfd.fd, SHUT_RDWR) 402 | 403 | fn close(inout self) -> Error: 404 | """Mark the socket closed. 405 | Once that happens, all future operations on the socket object will fail. 406 | The remote end will receive no more data (after queued data is flushed). 407 | """ 408 | self.shutdown() 409 | var err = self.sockfd.close() 410 | if err: 411 | return err 412 | 413 | self._closed = True 414 | return Error() 415 | 416 | # TODO: Trying to set timeout fails, but some other options don't? 417 | # fn get_timeout(self) raises -> Seconds: 418 | # """Return the timeout value for the socket.""" 419 | # return self.get_socket_option(SocketOptions.SO_RCVTIMEO) 420 | 421 | # fn set_timeout(self, owned duration: Seconds) raises: 422 | # """Set the timeout value for the socket. 423 | 424 | # Args: 425 | # duration: Seconds - The timeout duration in seconds. 426 | # """ 427 | # self.set_socket_option(SocketOptions.SO_RCVTIMEO, duration) 428 | 429 | fn send_file(self, file: FileHandle, offset: Int = 0) raises: 430 | self.send_all(file.read_bytes()) 431 | -------------------------------------------------------------------------------- /external/gojo/net/tcp.mojo: -------------------------------------------------------------------------------- 1 | from ..builtins import Byte 2 | from ..syscall import SocketOptions 3 | from .net import Connection, Conn 4 | from .address import TCPAddr, NetworkType, split_host_port 5 | from .socket import Socket 6 | 7 | 8 | # Time in nanoseconds 9 | alias Duration = Int 10 | alias DEFAULT_BUFFER_SIZE = 4096 11 | alias DEFAULT_TCP_KEEP_ALIVE = Duration(15 * 1000 * 1000 * 1000) # 15 seconds 12 | 13 | 14 | fn resolve_internet_addr(network: String, address: String) raises -> TCPAddr: 15 | var host: String = "" 16 | var port: String = "" 17 | var portnum: Int = 0 18 | if ( 19 | network == NetworkType.tcp.value 20 | or network == NetworkType.tcp4.value 21 | or network == NetworkType.tcp6.value 22 | or network == NetworkType.udp.value 23 | or network == NetworkType.udp4.value 24 | or network == NetworkType.udp6.value 25 | ): 26 | if address != "": 27 | var host_port = split_host_port(address) 28 | host = host_port.host 29 | port = str(host_port.port) 30 | portnum = atol(port.__str__()) 31 | elif network == NetworkType.ip.value or network == NetworkType.ip4.value or network == NetworkType.ip6.value: 32 | if address != "": 33 | host = address 34 | elif network == NetworkType.unix.value: 35 | raise Error("Unix addresses not supported yet") 36 | else: 37 | raise Error("unsupported network type: " + network) 38 | return TCPAddr(host, portnum) 39 | 40 | 41 | # TODO: For now listener is paired with TCP until we need to support 42 | # more than one type of Connection or Listener 43 | @value 44 | struct ListenConfig(CollectionElement): 45 | var keep_alive: Duration 46 | 47 | fn listen(self, network: String, address: String) raises -> TCPListener: 48 | var tcp_addr = resolve_internet_addr(network, address) 49 | var socket = Socket(local_address=tcp_addr) 50 | socket.bind(tcp_addr.ip, tcp_addr.port) 51 | socket.set_socket_option(SocketOptions.SO_REUSEADDR, 1) 52 | socket.listen() 53 | print(str("Listening on ") + str(socket.local_address)) 54 | return TCPListener(socket^, self, network, address) 55 | 56 | 57 | trait Listener(Movable): 58 | # Raising here because a Result[Optional[Connection], Error] is funky. 59 | fn accept(self) raises -> Connection: 60 | ... 61 | 62 | fn close(inout self) -> Error: 63 | ... 64 | 65 | fn addr(self) raises -> TCPAddr: 66 | ... 67 | 68 | 69 | struct TCPConnection(Conn): 70 | """TCPConn is an implementation of the Conn interface for TCP network connections. 71 | 72 | Args: 73 | connection: The underlying Connection. 74 | """ 75 | 76 | var _connection: Connection 77 | 78 | fn __init__(inout self, owned connection: Connection): 79 | self._connection = connection^ 80 | 81 | fn __init__(inout self, owned socket: Socket): 82 | self._connection = Connection(socket^) 83 | 84 | fn __moveinit__(inout self, owned existing: Self): 85 | self._connection = existing._connection^ 86 | 87 | fn read(inout self, inout dest: List[Byte]) -> (Int, Error): 88 | """Reads data from the underlying file descriptor. 89 | 90 | Args: 91 | dest: The buffer to read data into. 92 | 93 | Returns: 94 | The number of bytes read, or an error if one occurred. 95 | """ 96 | var bytes_read: Int 97 | var err: Error 98 | bytes_read, err = self._connection.read(dest) 99 | if err: 100 | if str(err) != io.EOF: 101 | return bytes_read, err 102 | 103 | return bytes_read, Error() 104 | 105 | fn write(inout self, src: List[Byte]) -> (Int, Error): 106 | """Writes data to the underlying file descriptor. 107 | 108 | Args: 109 | src: The buffer to read data into. 110 | 111 | Returns: 112 | The number of bytes written, or an error if one occurred. 113 | """ 114 | return self._connection.write(src) 115 | 116 | fn close(inout self) -> Error: 117 | """Closes the underlying file descriptor. 118 | 119 | Returns: 120 | An error if one occurred, or None if the file descriptor was closed successfully. 121 | """ 122 | return self._connection.close() 123 | 124 | fn local_address(self) -> TCPAddr: 125 | """Returns the local network address. 126 | The Addr returned is shared by all invocations of local_address, so do not modify it. 127 | 128 | Returns: 129 | The local network address. 130 | """ 131 | return self._connection.local_address() 132 | 133 | fn remote_address(self) -> TCPAddr: 134 | """Returns the remote network address. 135 | The Addr returned is shared by all invocations of remote_address, so do not modify it. 136 | 137 | Returns: 138 | The remote network address. 139 | """ 140 | return self._connection.remote_address() 141 | 142 | 143 | fn listen_tcp(network: String, local_address: TCPAddr) raises -> TCPListener: 144 | """Creates a new TCP listener. 145 | 146 | Args: 147 | network: The network type. 148 | local_address: The local address to listen on. 149 | """ 150 | return ListenConfig(DEFAULT_TCP_KEEP_ALIVE).listen(network, local_address.ip + ":" + str(local_address.port)) 151 | 152 | 153 | fn listen_tcp(network: String, local_address: String) raises -> TCPListener: 154 | """Creates a new TCP listener. 155 | 156 | Args: 157 | network: The network type. 158 | local_address: The address to listen on. The format is "host:port". 159 | """ 160 | return ListenConfig(DEFAULT_TCP_KEEP_ALIVE).listen(network, local_address) 161 | 162 | 163 | struct TCPListener(Listener): 164 | var _file_descriptor: Socket 165 | var listen_config: ListenConfig 166 | var network_type: String 167 | var address: String 168 | 169 | fn __init__( 170 | inout self, 171 | owned file_descriptor: Socket, 172 | listen_config: ListenConfig, 173 | network_type: String, 174 | address: String, 175 | ): 176 | self._file_descriptor = file_descriptor^ 177 | self.listen_config = listen_config 178 | self.network_type = network_type 179 | self.address = address 180 | 181 | fn __moveinit__(inout self, owned existing: Self): 182 | self._file_descriptor = existing._file_descriptor^ 183 | self.listen_config = existing.listen_config^ 184 | self.network_type = existing.network_type^ 185 | self.address = existing.address^ 186 | 187 | fn listen(self) raises -> Self: 188 | return self.listen_config.listen(self.network_type, self.address) 189 | 190 | fn accept(self) raises -> Connection: 191 | return Connection(self._file_descriptor.accept()) 192 | 193 | fn accept_tcp(self) raises -> TCPConnection: 194 | return TCPConnection(self._file_descriptor.accept()) 195 | 196 | fn close(inout self) -> Error: 197 | return self._file_descriptor.close() 198 | 199 | fn addr(self) raises -> TCPAddr: 200 | return resolve_internet_addr(self.network_type, self.address) 201 | -------------------------------------------------------------------------------- /external/gojo/strings/__init__.mojo: -------------------------------------------------------------------------------- 1 | from .builder import StringBuilder 2 | from .reader import Reader, new_reader 3 | -------------------------------------------------------------------------------- /external/gojo/strings/builder.mojo: -------------------------------------------------------------------------------- 1 | import ..io 2 | from ..builtins import Byte 3 | 4 | 5 | struct StringBuilder[growth_factor: Float32 = 2]( 6 | Stringable, 7 | Sized, 8 | io.Writer, 9 | io.StringWriter, 10 | io.ByteWriter, 11 | ): 12 | """ 13 | A string builder class that allows for efficient string management and concatenation. 14 | This class is useful when you need to build a string by appending multiple strings 15 | together. The performance increase is not linear. Compared to string concatenation, 16 | I've observed around 20-30x faster for writing and rending ~4KB and up to 2100x-2300x 17 | for ~4MB. This is because it avoids the overhead of creating and destroying many 18 | intermediate strings and performs memcopy operations. 19 | 20 | The result is a more efficient when building larger string concatenations. It 21 | is generally not recommended to use this class for small concatenations such as 22 | a few strings like `a + b + c + d` because the overhead of creating the string 23 | builder and appending the strings is not worth the performance gain. 24 | 25 | Example: 26 | ``` 27 | from strings.builder import StringBuilder 28 | 29 | var sb = StringBuilder() 30 | sb.write_string("Hello ") 31 | sb.write_string("World!") 32 | print(sb) # Hello World! 33 | ``` 34 | """ 35 | 36 | var data: DTypePointer[DType.uint8] 37 | var size: Int 38 | var capacity: Int 39 | 40 | @always_inline 41 | fn __init__(inout self, *, capacity: Int = 4096): 42 | constrained[growth_factor >= 1.25]() 43 | self.data = DTypePointer[DType.uint8]().alloc(capacity) 44 | self.size = 0 45 | self.capacity = capacity 46 | 47 | @always_inline 48 | fn __moveinit__(inout self, owned other: Self): 49 | self.data = other.data 50 | self.size = other.size 51 | self.capacity = other.capacity 52 | other.data = DTypePointer[DType.uint8]() 53 | other.size = 0 54 | other.capacity = 0 55 | 56 | @always_inline 57 | fn __del__(owned self): 58 | if self.data: 59 | self.data.free() 60 | 61 | @always_inline 62 | fn __len__(self) -> Int: 63 | """ 64 | Returns the length of the string builder. 65 | 66 | Returns: 67 | The length of the string builder. 68 | """ 69 | return self.size 70 | 71 | @always_inline 72 | fn __str__(self) -> String: 73 | """ 74 | Converts the string builder to a string. 75 | 76 | Returns: 77 | The string representation of the string builder. Returns an empty 78 | string if the string builder is empty. 79 | """ 80 | var copy = DTypePointer[DType.uint8]().alloc(self.size) 81 | memcpy(copy, self.data, self.size) 82 | return StringRef(copy, self.size) 83 | 84 | @always_inline 85 | fn render( 86 | self: Reference[Self], 87 | ) -> StringSlice[self.is_mutable, self.lifetime]: 88 | """ 89 | Return a StringSlice view of the data owned by the builder. 90 | Slightly faster than __str__, 10-20% faster in limited testing. 91 | 92 | Returns: 93 | The string representation of the string builder. Returns an empty string if the string builder is empty. 94 | """ 95 | return StringSlice[self.is_mutable, self.lifetime](unsafe_from_utf8_strref=StringRef(self[].data, self[].size)) 96 | 97 | @always_inline 98 | fn _resize(inout self, capacity: Int) -> None: 99 | """ 100 | Resizes the string builder buffer. 101 | 102 | Args: 103 | capacity: The new capacity of the string builder buffer. 104 | """ 105 | var new_data = DTypePointer[DType.uint8]().alloc(capacity) 106 | memcpy(new_data, self.data, self.size) 107 | self.data.free() 108 | self.data = new_data 109 | self.capacity = capacity 110 | 111 | return None 112 | 113 | @always_inline 114 | fn _write(inout self, src: Span[Byte]) -> (Int, Error): 115 | """ 116 | Appends a byte Span to the builder buffer. 117 | 118 | Args: 119 | src: The byte array to append. 120 | """ 121 | if len(src) > self.capacity - self.size: 122 | var new_capacity = int(self.capacity * growth_factor) 123 | if new_capacity < self.capacity + len(src): 124 | new_capacity = self.capacity + len(src) 125 | self._resize(new_capacity) 126 | 127 | memcpy(self.data.offset(self.size), src._data, len(src)) 128 | self.size += len(src) 129 | 130 | return len(src), Error() 131 | 132 | @always_inline 133 | fn write(inout self, src: List[Byte]) -> (Int, Error): 134 | """ 135 | Appends a byte Span to the builder buffer. 136 | 137 | Args: 138 | src: The byte array to append. 139 | """ 140 | var span = Span(src) 141 | return self._write(span) 142 | 143 | @always_inline 144 | fn write_string(inout self, src: String) -> (Int, Error): 145 | """ 146 | Appends a string to the builder buffer. 147 | 148 | Args: 149 | src: The string to append. 150 | """ 151 | return self._write(src.as_bytes_slice()) 152 | 153 | @always_inline 154 | fn write_byte(inout self, byte: UInt8) -> (Int, Error): 155 | var span = Span(List[UInt8](byte)) 156 | return self._write(span) 157 | -------------------------------------------------------------------------------- /external/gojo/strings/reader.mojo: -------------------------------------------------------------------------------- 1 | import ..io 2 | from ..builtins import Byte, copy, panic 3 | 4 | 5 | @value 6 | struct Reader( 7 | Sized, 8 | io.Reader, 9 | io.ReaderAt, 10 | io.ByteReader, 11 | io.ByteScanner, 12 | io.Seeker, 13 | io.WriterTo, 14 | ): 15 | """A Reader that implements the [io.Reader], [io.ReaderAt], [io.ByteReader], [io.ByteScanner], [io.Seeker], and [io.WriterTo] traits 16 | by reading from a string. The zero value for Reader operates like a Reader of an empty string. 17 | """ 18 | 19 | var string: String 20 | var read_pos: Int64 # current reading index 21 | var prev_rune: Int # index of previous rune; or < 0 22 | 23 | fn __init__(inout self, string: String = ""): 24 | self.string = string 25 | self.read_pos = 0 26 | self.prev_rune = -1 27 | 28 | fn __len__(self) -> Int: 29 | """Returns the number of bytes of the unread portion of the string. 30 | 31 | Returns: 32 | int: the number of bytes of the unread portion of the string. 33 | """ 34 | if self.read_pos >= Int64(len(self.string)): 35 | return 0 36 | 37 | return int(Int64(len(self.string)) - self.read_pos) 38 | 39 | fn size(self) -> Int64: 40 | """Returns the original length of the underlying string. 41 | size is the number of bytes available for reading via [Reader.read_at]. 42 | The returned value is always the same and is not affected by calls 43 | to any other method. 44 | 45 | Returns: 46 | The original length of the underlying string. 47 | """ 48 | return Int64(len(self.string)) 49 | 50 | fn read(inout self, inout dest: List[Byte]) -> (Int, Error): 51 | """Reads from the underlying string into the provided List[Byte] object. 52 | Implements the [io.Reader] trait. 53 | 54 | Args: 55 | dest: The destination List[Byte] object to read into. 56 | 57 | Returns: 58 | The number of bytes read into dest. 59 | """ 60 | if self.read_pos >= Int64(len(self.string)): 61 | return 0, Error(io.EOF) 62 | 63 | self.prev_rune = -1 64 | var bytes_written = copy(dest, self.string[int(self.read_pos) :].as_bytes()) 65 | self.read_pos += Int64(bytes_written) 66 | return bytes_written, Error() 67 | 68 | fn read_at(self, inout dest: List[Byte], off: Int64) -> (Int, Error): 69 | """Reads from the Reader into the dest List[Byte] starting at the offset off. 70 | It returns the number of bytes read into dest and an error if any. 71 | Implements the [io.ReaderAt] trait. 72 | 73 | Args: 74 | dest: The destination List[Byte] object to read into. 75 | off: The byte offset to start reading from. 76 | 77 | Returns: 78 | The number of bytes read into dest. 79 | """ 80 | # cannot modify state - see io.ReaderAt 81 | if off < 0: 82 | return 0, Error("strings.Reader.read_at: negative offset") 83 | 84 | if off >= Int64(len(self.string)): 85 | return 0, Error(io.EOF) 86 | 87 | var error = Error() 88 | var copied_elements_count = copy(dest, self.string[int(off) :].as_bytes()) 89 | if copied_elements_count < len(dest): 90 | error = Error(io.EOF) 91 | 92 | return copied_elements_count, error 93 | 94 | fn read_byte(inout self) -> (Byte, Error): 95 | """Reads the next byte from the underlying string. 96 | Implements the [io.ByteReader] trait. 97 | 98 | Returns: 99 | The next byte from the underlying string. 100 | """ 101 | self.prev_rune = -1 102 | if self.read_pos >= Int64(len(self.string)): 103 | return Byte(0), Error(io.EOF) 104 | 105 | var b = self.string[int(self.read_pos)] 106 | self.read_pos += 1 107 | return Byte(ord(b)), Error() 108 | 109 | fn unread_byte(inout self) -> Error: 110 | """Unreads the last byte read. Only the most recent byte read can be unread. 111 | Implements the [io.ByteScanner] trait. 112 | """ 113 | if self.read_pos <= 0: 114 | return Error("strings.Reader.unread_byte: at beginning of string") 115 | 116 | self.prev_rune = -1 117 | self.read_pos -= 1 118 | 119 | return Error() 120 | 121 | # # read_rune implements the [io.RuneReader] trait. 122 | # fn read_rune() (ch rune, size int, err error): 123 | # if self.read_pos >= Int64(len(self.string)): 124 | # self.prev_rune = -1 125 | # return 0, 0, io.EOF 126 | 127 | # self.prev_rune = int(self.read_pos) 128 | # if c = self.string[self.read_pos]; c < utf8.RuneSelf: 129 | # self.read_pos += 1 130 | # return rune(c), 1, nil 131 | 132 | # ch, size = utf8.DecodeRuneInString(self.string[self.read_pos:]) 133 | # self.read_pos += Int64(size) 134 | # return 135 | 136 | # # unread_rune implements the [io.RuneScanner] trait. 137 | # fn unread_rune() error: 138 | # if self.read_pos <= 0: 139 | # return errors.New("strings.Reader.unread_rune: at beginning of string") 140 | 141 | # if self.prev_rune < 0: 142 | # return errors.New("strings.Reader.unread_rune: previous operation was not read_rune") 143 | 144 | # self.read_pos = Int64(self.prev_rune) 145 | # self.prev_rune = -1 146 | # return nil 147 | 148 | fn seek(inout self, offset: Int64, whence: Int) -> (Int64, Error): 149 | """Seeks to a new position in the underlying string. The next read will start from that position. 150 | Implements the [io.Seeker] trait. 151 | 152 | Args: 153 | offset: The offset to seek to. 154 | whence: The seek mode. It can be one of [io.SEEK_START], [io.SEEK_CURRENT], or [io.SEEK_END]. 155 | 156 | Returns: 157 | The new position in the string. 158 | """ 159 | self.prev_rune = -1 160 | var position: Int64 = 0 161 | 162 | if whence == io.SEEK_START: 163 | position = offset 164 | elif whence == io.SEEK_CURRENT: 165 | position = self.read_pos + offset 166 | elif whence == io.SEEK_END: 167 | position = Int64(len(self.string)) + offset 168 | else: 169 | return Int64(0), Error("strings.Reader.seek: invalid whence") 170 | 171 | if position < 0: 172 | return Int64(0), Error("strings.Reader.seek: negative position") 173 | 174 | self.read_pos = position 175 | return position, Error() 176 | 177 | fn write_to[W: io.Writer](inout self, inout writer: W) -> (Int64, Error): 178 | """Writes the remaining portion of the underlying string to the provided writer. 179 | Implements the [io.WriterTo] trait. 180 | 181 | Args: 182 | writer: The writer to write the remaining portion of the string to. 183 | 184 | Returns: 185 | The number of bytes written to the writer. 186 | """ 187 | self.prev_rune = -1 188 | if self.read_pos >= Int64(len(self.string)): 189 | return Int64(0), Error() 190 | 191 | var chunk_to_write = self.string[int(self.read_pos) :] 192 | var bytes_written: Int 193 | var err: Error 194 | bytes_written, err = io.write_string(writer, chunk_to_write) 195 | if bytes_written > len(chunk_to_write): 196 | panic("strings.Reader.write_to: invalid write_string count") 197 | 198 | self.read_pos += Int64(bytes_written) 199 | if bytes_written != len(chunk_to_write) and not err: 200 | err = Error(io.ERR_SHORT_WRITE) 201 | 202 | return Int64(bytes_written), err 203 | 204 | # TODO: How can I differentiate between the two write_to methods when the writer implements both traits? 205 | # fn write_to[W: io.StringWriter](inout self, inout writer: W) raises -> Int64: 206 | # """Writes the remaining portion of the underlying string to the provided writer. 207 | # Implements the [io.WriterTo] trait. 208 | 209 | # Args: 210 | # writer: The writer to write the remaining portion of the string to. 211 | 212 | # Returns: 213 | # The number of bytes written to the writer. 214 | # """ 215 | # self.prev_rune = -1 216 | # if self.read_pos >= Int64(len(self.string)): 217 | # return 0 218 | 219 | # var chunk_to_write = self.string[self.read_pos:] 220 | # var bytes_written = io.write_string(writer, chunk_to_write) 221 | # if bytes_written > len(chunk_to_write): 222 | # raise Error("strings.Reader.write_to: invalid write_string count") 223 | 224 | # self.read_pos += Int64(bytes_written) 225 | # if bytes_written != len(chunk_to_write): 226 | # raise Error(io.ERR_SHORT_WRITE) 227 | 228 | # return Int64(bytes_written) 229 | 230 | fn reset(inout self, string: String): 231 | """Resets the [Reader] to be reading from the beginning of the provided string. 232 | 233 | Args: 234 | string: The string to read from. 235 | """ 236 | self.string = string 237 | self.read_pos = 0 238 | self.prev_rune = -1 239 | 240 | 241 | fn new_reader(string: String = "") -> Reader: 242 | """Returns a new [Reader] reading from the provided string. 243 | It is similar to [bytes.new_buffer] but more efficient and non-writable. 244 | 245 | Args: 246 | string: The string to read from. 247 | """ 248 | return Reader(string) 249 | -------------------------------------------------------------------------------- /external/gojo/syscall/__init__.mojo: -------------------------------------------------------------------------------- 1 | from .net import ( 2 | FD, 3 | SocketType, 4 | AddressFamily, 5 | ProtocolFamily, 6 | SocketOptions, 7 | AddressInformation, 8 | send, 9 | recv, 10 | open, 11 | addrinfo, 12 | addrinfo_unix, 13 | sockaddr, 14 | sockaddr_in, 15 | socklen_t, 16 | socket, 17 | connect, 18 | htons, 19 | ntohs, 20 | inet_pton, 21 | inet_ntop, 22 | getaddrinfo, 23 | getaddrinfo_unix, 24 | gai_strerror, 25 | to_char_ptr, 26 | c_charptr_to_string, 27 | shutdown, 28 | inet_ntoa, 29 | bind, 30 | listen, 31 | accept, 32 | setsockopt, 33 | getsockopt, 34 | getsockname, 35 | getpeername, 36 | SHUT_RDWR, 37 | SOL_SOCKET, 38 | ) 39 | from .file import close, FileDescriptorBase 40 | 41 | # Adapted from https://github.com/crisadamo/mojo-Libc . Huge thanks to Cristian! 42 | # C types 43 | alias c_void = UInt8 44 | alias c_char = UInt8 45 | alias c_schar = Int8 46 | alias c_uchar = UInt8 47 | alias c_short = Int16 48 | alias c_ushort = UInt16 49 | alias c_int = Int32 50 | alias c_uint = UInt32 51 | alias c_long = Int64 52 | alias c_ulong = UInt64 53 | alias c_float = Float32 54 | alias c_double = Float64 55 | 56 | # `Int` is known to be machine's width 57 | alias c_size_t = Int 58 | alias c_ssize_t = Int 59 | 60 | alias ptrdiff_t = Int64 61 | alias intptr_t = Int64 62 | alias uintptr_t = UInt64 63 | 64 | 65 | fn strlen(s: DTypePointer[DType.uint8]) -> c_size_t: 66 | """Libc POSIX `strlen` function 67 | Reference: https://man7.org/linux/man-pages/man3/strlen.3p.html 68 | Fn signature: size_t strlen(const char *s). 69 | 70 | Args: s: A pointer to a C string. 71 | Returns: The length of the string. 72 | """ 73 | return external_call["strlen", c_size_t, DTypePointer[DType.uint8]](s) 74 | -------------------------------------------------------------------------------- /external/gojo/syscall/file.mojo: -------------------------------------------------------------------------------- 1 | from . import c_int, c_char, c_void, c_size_t, c_ssize_t 2 | 3 | 4 | trait FileDescriptorBase(io.Reader, io.Writer, io.Closer): 5 | ... 6 | 7 | 8 | # --- ( File Related Syscalls & Structs )--------------------------------------- 9 | alias O_NONBLOCK = 16384 10 | alias O_ACCMODE = 3 11 | alias O_CLOEXEC = 524288 12 | 13 | 14 | fn close(fildes: c_int) -> c_int: 15 | """Libc POSIX `close` function 16 | Reference: https://man7.org/linux/man-pages/man3/close.3p.html 17 | Fn signature: int close(int fildes). 18 | 19 | Args: 20 | fildes: A File Descriptor to close. 21 | 22 | Returns: 23 | Upon successful completion, 0 shall be returned; otherwise, -1 24 | shall be returned and errno set to indicate the error. 25 | """ 26 | return external_call["close", c_int, c_int](fildes) 27 | 28 | 29 | fn open[*T: AnyType](path: UnsafePointer[c_char], oflag: c_int) -> c_int: 30 | """Libc POSIX `open` function 31 | Reference: https://man7.org/linux/man-pages/man3/open.3p.html 32 | Fn signature: int open(const char *path, int oflag, ...). 33 | 34 | Args: 35 | path: A pointer to a C string containing the path to open. 36 | oflag: The flags to open the file with. 37 | Returns: 38 | A File Descriptor or -1 in case of failure 39 | """ 40 | return external_call["open", c_int, UnsafePointer[c_char], c_int](path, oflag) # FnName, RetType # Args 41 | 42 | 43 | fn read(fildes: c_int, buf: UnsafePointer[c_void], nbyte: c_size_t) -> c_int: 44 | """Libc POSIX `read` function 45 | Reference: https://man7.org/linux/man-pages/man3/read.3p.html 46 | Fn signature: sssize_t read(int fildes, void *buf, size_t nbyte). 47 | 48 | Args: fildes: A File Descriptor. 49 | buf: A pointer to a buffer to store the read data. 50 | nbyte: The number of bytes to read. 51 | Returns: The number of bytes read or -1 in case of failure. 52 | """ 53 | return external_call["read", c_ssize_t, c_int, UnsafePointer[c_void], c_size_t](fildes, buf, nbyte) 54 | 55 | 56 | fn write(fildes: c_int, buf: UnsafePointer[c_void], nbyte: c_size_t) -> c_int: 57 | """Libc POSIX `write` function 58 | Reference: https://man7.org/linux/man-pages/man3/write.3p.html 59 | Fn signature: ssize_t write(int fildes, const void *buf, size_t nbyte). 60 | 61 | Args: fildes: A File Descriptor. 62 | buf: A pointer to a buffer to write. 63 | nbyte: The number of bytes to write. 64 | Returns: The number of bytes written or -1 in case of failure. 65 | """ 66 | return external_call["write", c_ssize_t, c_int, UnsafePointer[c_void], c_size_t](fildes, buf, nbyte) 67 | -------------------------------------------------------------------------------- /external/gojo/syscall/net.mojo: -------------------------------------------------------------------------------- 1 | from . import c_char, c_int, c_ushort, c_uint, c_size_t, c_ssize_t, strlen 2 | from .file import O_CLOEXEC, O_NONBLOCK 3 | from utils.static_tuple import StaticTuple 4 | 5 | alias IPPROTO_IPV6 = 41 6 | alias IPV6_V6ONLY = 26 7 | alias EPROTONOSUPPORT = 93 8 | 9 | # Adapted from https://github.com/gabrieldemarmiesse/mojo-stdlib-extensions/ . Huge thanks to Gabriel! 10 | 11 | 12 | struct FD: 13 | alias FD_STDIN = 0 14 | alias FD_STDOUT = 1 15 | alias FD_STDERR = 2 16 | 17 | 18 | alias SUCCESS = 0 19 | alias GRND_NONBLOCK: UInt8 = 1 20 | 21 | alias char_pointer = DTypePointer[DType.uint8] 22 | 23 | 24 | # --- ( error.h Constants )----------------------------------------------------- 25 | struct ErrnoConstants: 26 | alias EPERM = 1 27 | alias ENOENT = 2 28 | alias ESRCH = 3 29 | alias EINTR = 4 30 | alias EIO = 5 31 | alias ENXIO = 6 32 | alias E2BIG = 7 33 | alias ENOEXEC = 8 34 | alias EBADF = 9 35 | alias ECHILD = 10 36 | alias EAGAIN = 11 37 | alias ENOMEM = 12 38 | alias EACCES = 13 39 | alias EFAULT = 14 40 | alias ENOTBLK = 15 41 | alias EBUSY = 16 42 | alias EEXIST = 17 43 | alias EXDEV = 18 44 | alias ENODEV = 19 45 | alias ENOTDIR = 20 46 | alias EISDIR = 21 47 | alias EINVAL = 22 48 | alias ENFILE = 23 49 | alias EMFILE = 24 50 | alias ENOTTY = 25 51 | alias ETXTBSY = 26 52 | alias EFBIG = 27 53 | alias ENOSPC = 28 54 | alias ESPIPE = 29 55 | alias EROFS = 30 56 | alias EMLINK = 31 57 | alias EPIPE = 32 58 | alias EDOM = 33 59 | alias ERANGE = 34 60 | alias EWOULDBLOCK = 11 61 | 62 | 63 | fn to_char_ptr(s: String) -> DTypePointer[DType.uint8]: 64 | """Only ASCII-based strings.""" 65 | var ptr = DTypePointer[DType.uint8]().alloc(len(s)) 66 | for i in range(len(s)): 67 | ptr.store(i, ord(s[i])) 68 | return ptr 69 | 70 | 71 | fn c_charptr_to_string(s: DTypePointer[DType.uint8]) -> String: 72 | return String(s, strlen(s)) 73 | 74 | 75 | fn cftob(val: c_int) -> Bool: 76 | """Convert C-like failure (-1) to Bool.""" 77 | return rebind[Bool](val > 0) 78 | 79 | 80 | # --- ( Network Related Constants )--------------------------------------------- 81 | alias sa_family_t = c_ushort 82 | alias socklen_t = c_uint 83 | alias in_addr_t = c_uint 84 | alias in_port_t = c_ushort 85 | 86 | 87 | # Address Family Constants 88 | struct AddressFamily: 89 | alias AF_UNSPEC = 0 90 | alias AF_UNIX = 1 91 | alias AF_LOCAL = 1 92 | alias AF_INET = 2 93 | alias AF_AX25 = 3 94 | alias AF_IPX = 4 95 | alias AF_APPLETALK = 5 96 | alias AF_NETROM = 6 97 | alias AF_BRIDGE = 7 98 | alias AF_ATMPVC = 8 99 | alias AF_X25 = 9 100 | alias AF_INET6 = 10 101 | alias AF_ROSE = 11 102 | alias AF_DECnet = 12 103 | alias AF_NETBEUI = 13 104 | alias AF_SECURITY = 14 105 | alias AF_KEY = 15 106 | alias AF_NETLINK = 16 107 | alias AF_ROUTE = 16 108 | alias AF_PACKET = 17 109 | alias AF_ASH = 18 110 | alias AF_ECONET = 19 111 | alias AF_ATMSVC = 20 112 | alias AF_RDS = 21 113 | alias AF_SNA = 22 114 | alias AF_IRDA = 23 115 | alias AF_PPPOX = 24 116 | alias AF_WANPIPE = 25 117 | alias AF_LLC = 26 118 | alias AF_CAN = 29 119 | alias AF_TIPC = 30 120 | alias AF_BLUETOOTH = 31 121 | alias AF_IUCV = 32 122 | alias AF_RXRPC = 33 123 | alias AF_ISDN = 34 124 | alias AF_PHONET = 35 125 | alias AF_IEEE802154 = 36 126 | alias AF_CAIF = 37 127 | alias AF_ALG = 38 128 | alias AF_NFC = 39 129 | alias AF_VSOCK = 40 130 | alias AF_KCM = 41 131 | alias AF_QIPCRTR = 42 132 | alias AF_MAX = 43 133 | 134 | 135 | # Protocol family constants 136 | struct ProtocolFamily: 137 | alias PF_UNSPEC = AddressFamily.AF_UNSPEC 138 | alias PF_UNIX = AddressFamily.AF_UNIX 139 | alias PF_LOCAL = AddressFamily.AF_LOCAL 140 | alias PF_INET = AddressFamily.AF_INET 141 | alias PF_AX25 = AddressFamily.AF_AX25 142 | alias PF_IPX = AddressFamily.AF_IPX 143 | alias PF_APPLETALK = AddressFamily.AF_APPLETALK 144 | alias PF_NETROM = AddressFamily.AF_NETROM 145 | alias PF_BRIDGE = AddressFamily.AF_BRIDGE 146 | alias PF_ATMPVC = AddressFamily.AF_ATMPVC 147 | alias PF_X25 = AddressFamily.AF_X25 148 | alias PF_INET6 = AddressFamily.AF_INET6 149 | alias PF_ROSE = AddressFamily.AF_ROSE 150 | alias PF_DECnet = AddressFamily.AF_DECnet 151 | alias PF_NETBEUI = AddressFamily.AF_NETBEUI 152 | alias PF_SECURITY = AddressFamily.AF_SECURITY 153 | alias PF_KEY = AddressFamily.AF_KEY 154 | alias PF_NETLINK = AddressFamily.AF_NETLINK 155 | alias PF_ROUTE = AddressFamily.AF_ROUTE 156 | alias PF_PACKET = AddressFamily.AF_PACKET 157 | alias PF_ASH = AddressFamily.AF_ASH 158 | alias PF_ECONET = AddressFamily.AF_ECONET 159 | alias PF_ATMSVC = AddressFamily.AF_ATMSVC 160 | alias PF_RDS = AddressFamily.AF_RDS 161 | alias PF_SNA = AddressFamily.AF_SNA 162 | alias PF_IRDA = AddressFamily.AF_IRDA 163 | alias PF_PPPOX = AddressFamily.AF_PPPOX 164 | alias PF_WANPIPE = AddressFamily.AF_WANPIPE 165 | alias PF_LLC = AddressFamily.AF_LLC 166 | alias PF_CAN = AddressFamily.AF_CAN 167 | alias PF_TIPC = AddressFamily.AF_TIPC 168 | alias PF_BLUETOOTH = AddressFamily.AF_BLUETOOTH 169 | alias PF_IUCV = AddressFamily.AF_IUCV 170 | alias PF_RXRPC = AddressFamily.AF_RXRPC 171 | alias PF_ISDN = AddressFamily.AF_ISDN 172 | alias PF_PHONET = AddressFamily.AF_PHONET 173 | alias PF_IEEE802154 = AddressFamily.AF_IEEE802154 174 | alias PF_CAIF = AddressFamily.AF_CAIF 175 | alias PF_ALG = AddressFamily.AF_ALG 176 | alias PF_NFC = AddressFamily.AF_NFC 177 | alias PF_VSOCK = AddressFamily.AF_VSOCK 178 | alias PF_KCM = AddressFamily.AF_KCM 179 | alias PF_QIPCRTR = AddressFamily.AF_QIPCRTR 180 | alias PF_MAX = AddressFamily.AF_MAX 181 | 182 | 183 | # Socket Type constants 184 | struct SocketType: 185 | alias SOCK_STREAM = 1 186 | alias SOCK_DGRAM = 2 187 | alias SOCK_RAW = 3 188 | alias SOCK_RDM = 4 189 | alias SOCK_SEQPACKET = 5 190 | alias SOCK_DCCP = 6 191 | alias SOCK_PACKET = 10 192 | alias SOCK_CLOEXEC = O_CLOEXEC 193 | alias SOCK_NONBLOCK = O_NONBLOCK 194 | 195 | 196 | # Address Information 197 | struct AddressInformation: 198 | alias AI_PASSIVE = 1 199 | alias AI_CANONNAME = 2 200 | alias AI_NUMERICHOST = 4 201 | alias AI_V4MAPPED = 2048 202 | alias AI_ALL = 256 203 | alias AI_ADDRCONFIG = 1024 204 | alias AI_IDN = 64 205 | 206 | 207 | alias INET_ADDRSTRLEN = 16 208 | alias INET6_ADDRSTRLEN = 46 209 | 210 | alias SHUT_RD = 0 211 | alias SHUT_WR = 1 212 | alias SHUT_RDWR = 2 213 | 214 | alias SOL_SOCKET = 65535 215 | 216 | 217 | # Socket Options 218 | struct SocketOptions: 219 | alias SO_DEBUG = 1 220 | alias SO_REUSEADDR = 4 221 | alias SO_TYPE = 4104 222 | alias SO_ERROR = 4103 223 | alias SO_DONTROUTE = 16 224 | alias SO_BROADCAST = 32 225 | alias SO_SNDBUF = 4097 226 | alias SO_RCVBUF = 4098 227 | alias SO_KEEPALIVE = 8 228 | alias SO_OOBINLINE = 256 229 | alias SO_LINGER = 128 230 | alias SO_REUSEPORT = 512 231 | alias SO_RCVLOWAT = 4100 232 | alias SO_SNDLOWAT = 4099 233 | alias SO_RCVTIMEO = 4102 234 | alias SO_SNDTIMEO = 4101 235 | alias SO_RCVTIMEO_OLD = 4102 236 | alias SO_SNDTIMEO_OLD = 4101 237 | alias SO_ACCEPTCONN = 2 238 | # unsure of these socket options, they weren't available via python 239 | alias SO_NO_CHECK = 11 240 | alias SO_PRIORITY = 12 241 | alias SO_BSDCOMPAT = 14 242 | alias SO_PASSCRED = 16 243 | alias SO_PEERCRED = 17 244 | alias SO_SECURITY_AUTHENTICATION = 22 245 | alias SO_SECURITY_ENCRYPTION_TRANSPORT = 23 246 | alias SO_SECURITY_ENCRYPTION_NETWORK = 24 247 | alias SO_BINDTODEVICE = 25 248 | alias SO_ATTACH_FILTER = 26 249 | alias SO_DETACH_FILTER = 27 250 | alias SO_GET_FILTER = 26 251 | alias SO_PEERNAME = 28 252 | alias SO_TIMESTAMP = 29 253 | alias SO_TIMESTAMP_OLD = 29 254 | alias SO_PEERSEC = 31 255 | alias SO_SNDBUFFORCE = 32 256 | alias SO_RCVBUFFORCE = 33 257 | alias SO_PASSSEC = 34 258 | alias SO_TIMESTAMPNS = 35 259 | alias SO_TIMESTAMPNS_OLD = 35 260 | alias SO_MARK = 36 261 | alias SO_TIMESTAMPING = 37 262 | alias SO_TIMESTAMPING_OLD = 37 263 | alias SO_PROTOCOL = 38 264 | alias SO_DOMAIN = 39 265 | alias SO_RXQ_OVFL = 40 266 | alias SO_WIFI_STATUS = 41 267 | alias SCM_WIFI_STATUS = 41 268 | alias SO_PEEK_OFF = 42 269 | alias SO_NOFCS = 43 270 | alias SO_LOCK_FILTER = 44 271 | alias SO_SELECT_ERR_QUEUE = 45 272 | alias SO_BUSY_POLL = 46 273 | alias SO_MAX_PACING_RATE = 47 274 | alias SO_BPF_EXTENSIONS = 48 275 | alias SO_INCOMING_CPU = 49 276 | alias SO_ATTACH_BPF = 50 277 | alias SO_DETACH_BPF = 27 278 | alias SO_ATTACH_REUSEPORT_CBPF = 51 279 | alias SO_ATTACH_REUSEPORT_EBPF = 52 280 | alias SO_CNX_ADVICE = 53 281 | alias SCM_TIMESTAMPING_OPT_STATS = 54 282 | alias SO_MEMINFO = 55 283 | alias SO_INCOMING_NAPI_ID = 56 284 | alias SO_COOKIE = 57 285 | alias SCM_TIMESTAMPING_PKTINFO = 58 286 | alias SO_PEERGROUPS = 59 287 | alias SO_ZEROCOPY = 60 288 | alias SO_TXTIME = 61 289 | alias SCM_TXTIME = 61 290 | alias SO_BINDTOIFINDEX = 62 291 | alias SO_TIMESTAMP_NEW = 63 292 | alias SO_TIMESTAMPNS_NEW = 64 293 | alias SO_TIMESTAMPING_NEW = 65 294 | alias SO_RCVTIMEO_NEW = 66 295 | alias SO_SNDTIMEO_NEW = 67 296 | alias SO_DETACH_REUSEPORT_BPF = 68 297 | 298 | 299 | # --- ( Network Related Structs )----------------------------------------------- 300 | @value 301 | @register_passable("trivial") 302 | struct in_addr: 303 | var s_addr: in_addr_t 304 | 305 | 306 | @value 307 | @register_passable("trivial") 308 | struct in6_addr: 309 | var s6_addr: StaticTuple[c_char, 16] 310 | 311 | 312 | @value 313 | @register_passable("trivial") 314 | struct sockaddr: 315 | var sa_family: sa_family_t 316 | var sa_data: StaticTuple[c_char, 14] 317 | 318 | 319 | @value 320 | @register_passable("trivial") 321 | struct sockaddr_in: 322 | var sin_family: sa_family_t 323 | var sin_port: in_port_t 324 | var sin_addr: in_addr 325 | var sin_zero: StaticTuple[c_char, 8] 326 | 327 | 328 | @value 329 | @register_passable("trivial") 330 | struct sockaddr_in6: 331 | var sin6_family: sa_family_t 332 | var sin6_port: in_port_t 333 | var sin6_flowinfo: c_uint 334 | var sin6_addr: in6_addr 335 | var sin6_scope_id: c_uint 336 | 337 | 338 | @value 339 | @register_passable("trivial") 340 | struct addrinfo: 341 | """Struct field ordering can vary based on platform. 342 | For MacOS, I had to swap the order of ai_canonname and ai_addr. 343 | https://stackoverflow.com/questions/53575101/calling-getaddrinfo-directly-from-python-ai-addr-is-null-pointer. 344 | """ 345 | 346 | var ai_flags: c_int 347 | var ai_family: c_int 348 | var ai_socktype: c_int 349 | var ai_protocol: c_int 350 | var ai_addrlen: socklen_t 351 | var ai_canonname: DTypePointer[DType.uint8] 352 | var ai_addr: UnsafePointer[sockaddr] 353 | var ai_next: UnsafePointer[addrinfo] 354 | 355 | fn __init__( 356 | inout self, 357 | ai_flags: c_int = 0, 358 | ai_family: c_int = 0, 359 | ai_socktype: c_int = 0, 360 | ai_protocol: c_int = 0, 361 | ai_addrlen: socklen_t = 0, 362 | ai_canonname: DTypePointer[DType.uint8] = DTypePointer[DType.uint8](), 363 | ai_addr: UnsafePointer[sockaddr] = UnsafePointer[sockaddr](), 364 | ai_next: UnsafePointer[addrinfo] = UnsafePointer[addrinfo](), 365 | ): 366 | self.ai_flags = ai_flags 367 | self.ai_family = ai_family 368 | self.ai_socktype = ai_socktype 369 | self.ai_protocol = ai_protocol 370 | self.ai_addrlen = ai_addrlen 371 | self.ai_canonname = ai_canonname 372 | self.ai_addr = ai_addr 373 | self.ai_next = ai_next 374 | 375 | # fn __init__() -> Self: 376 | # return Self(0, 0, 0, 0, 0, DTypePointer[DType.uint8](), UnsafePointer[sockaddr](), UnsafePointer[addrinfo]()) 377 | 378 | 379 | @value 380 | @register_passable("trivial") 381 | struct addrinfo_unix: 382 | """Struct field ordering can vary based on platform. 383 | For MacOS, I had to swap the order of ai_canonname and ai_addr. 384 | https://stackoverflow.com/questions/53575101/calling-getaddrinfo-directly-from-python-ai-addr-is-null-pointer. 385 | """ 386 | 387 | var ai_flags: c_int 388 | var ai_family: c_int 389 | var ai_socktype: c_int 390 | var ai_protocol: c_int 391 | var ai_addrlen: socklen_t 392 | var ai_addr: UnsafePointer[sockaddr] 393 | var ai_canonname: DTypePointer[DType.uint8] 394 | var ai_next: UnsafePointer[addrinfo] 395 | 396 | fn __init__( 397 | inout self, 398 | ai_flags: c_int = 0, 399 | ai_family: c_int = 0, 400 | ai_socktype: c_int = 0, 401 | ai_protocol: c_int = 0, 402 | ai_addrlen: socklen_t = 0, 403 | ai_canonname: DTypePointer[DType.uint8] = DTypePointer[DType.uint8](), 404 | ai_addr: UnsafePointer[sockaddr] = UnsafePointer[sockaddr](), 405 | ai_next: UnsafePointer[addrinfo] = UnsafePointer[addrinfo](), 406 | ): 407 | self.ai_flags = ai_flags 408 | self.ai_family = ai_family 409 | self.ai_socktype = ai_socktype 410 | self.ai_protocol = ai_protocol 411 | self.ai_addrlen = ai_addrlen 412 | self.ai_canonname = ai_canonname 413 | self.ai_addr = ai_addr 414 | self.ai_next = ai_next 415 | 416 | 417 | # --- ( Network Related Syscalls & Structs )------------------------------------ 418 | 419 | 420 | fn htonl(hostlong: c_uint) -> c_uint: 421 | """Libc POSIX `htonl` function 422 | Reference: https://man7.org/linux/man-pages/man3/htonl.3p.html 423 | Fn signature: uint32_t htonl(uint32_t hostlong). 424 | 425 | Args: hostlong: A 32-bit integer in host byte order. 426 | Returns: The value provided in network byte order. 427 | """ 428 | return external_call["htonl", c_uint, c_uint](hostlong) 429 | 430 | 431 | fn htons(hostshort: c_ushort) -> c_ushort: 432 | """Libc POSIX `htons` function 433 | Reference: https://man7.org/linux/man-pages/man3/htonl.3p.html 434 | Fn signature: uint16_t htons(uint16_t hostshort). 435 | 436 | Args: hostshort: A 16-bit integer in host byte order. 437 | Returns: The value provided in network byte order. 438 | """ 439 | return external_call["htons", c_ushort, c_ushort](hostshort) 440 | 441 | 442 | fn ntohl(netlong: c_uint) -> c_uint: 443 | """Libc POSIX `ntohl` function 444 | Reference: https://man7.org/linux/man-pages/man3/htonl.3p.html 445 | Fn signature: uint32_t ntohl(uint32_t netlong). 446 | 447 | Args: netlong: A 32-bit integer in network byte order. 448 | Returns: The value provided in host byte order. 449 | """ 450 | return external_call["ntohl", c_uint, c_uint](netlong) 451 | 452 | 453 | fn ntohs(netshort: c_ushort) -> c_ushort: 454 | """Libc POSIX `ntohs` function 455 | Reference: https://man7.org/linux/man-pages/man3/htonl.3p.html 456 | Fn signature: uint16_t ntohs(uint16_t netshort). 457 | 458 | Args: netshort: A 16-bit integer in network byte order. 459 | Returns: The value provided in host byte order. 460 | """ 461 | return external_call["ntohs", c_ushort, c_ushort](netshort) 462 | 463 | 464 | fn inet_ntop( 465 | af: c_int, 466 | src: DTypePointer[DType.uint8], 467 | dst: DTypePointer[DType.uint8], 468 | size: socklen_t, 469 | ) -> DTypePointer[DType.uint8]: 470 | """Libc POSIX `inet_ntop` function 471 | Reference: https://man7.org/linux/man-pages/man3/inet_ntop.3p.html. 472 | Fn signature: const char *inet_ntop(int af, const void *restrict src, char *restrict dst, socklen_t size). 473 | 474 | Args: 475 | af: Address Family see AF_ aliases. 476 | src: A pointer to a binary address. 477 | dst: A pointer to a buffer to store the result. 478 | size: The size of the buffer. 479 | 480 | Returns: 481 | A pointer to the buffer containing the result. 482 | """ 483 | return external_call[ 484 | "inet_ntop", 485 | DTypePointer[DType.uint8], # FnName, RetType 486 | c_int, 487 | DTypePointer[DType.uint8], 488 | DTypePointer[DType.uint8], 489 | socklen_t, # Args 490 | ](af, src, dst, size) 491 | 492 | 493 | fn inet_pton(af: c_int, src: DTypePointer[DType.uint8], dst: DTypePointer[DType.uint8]) -> c_int: 494 | """Libc POSIX `inet_pton` function 495 | Reference: https://man7.org/linux/man-pages/man3/inet_ntop.3p.html 496 | Fn signature: int inet_pton(int af, const char *restrict src, void *restrict dst). 497 | 498 | Args: af: Address Family see AF_ aliases. 499 | src: A pointer to a string containing the address. 500 | dst: A pointer to a buffer to store the result. 501 | Returns: 1 on success, 0 if the input is not a valid address, -1 on error. 502 | """ 503 | return external_call[ 504 | "inet_pton", 505 | c_int, # FnName, RetType 506 | c_int, 507 | DTypePointer[DType.uint8], 508 | DTypePointer[DType.uint8], # Args 509 | ](af, src, dst) 510 | 511 | 512 | fn inet_addr(cp: DTypePointer[DType.uint8]) -> in_addr_t: 513 | """Libc POSIX `inet_addr` function 514 | Reference: https://man7.org/linux/man-pages/man3/inet_addr.3p.html 515 | Fn signature: in_addr_t inet_addr(const char *cp). 516 | 517 | Args: cp: A pointer to a string containing the address. 518 | Returns: The address in network byte order. 519 | """ 520 | return external_call["inet_addr", in_addr_t, DTypePointer[DType.uint8]](cp) 521 | 522 | 523 | fn inet_ntoa(addr: in_addr) -> DTypePointer[DType.uint8]: 524 | """Libc POSIX `inet_ntoa` function 525 | Reference: https://man7.org/linux/man-pages/man3/inet_addr.3p.html 526 | Fn signature: char *inet_ntoa(struct in_addr in). 527 | 528 | Args: in: A pointer to a string containing the address. 529 | Returns: The address in network byte order. 530 | """ 531 | return external_call["inet_ntoa", DTypePointer[DType.uint8], in_addr](addr) 532 | 533 | 534 | fn socket(domain: c_int, type: c_int, protocol: c_int) -> c_int: 535 | """Libc POSIX `socket` function 536 | Reference: https://man7.org/linux/man-pages/man3/socket.3p.html 537 | Fn signature: int socket(int domain, int type, int protocol). 538 | 539 | Args: domain: Address Family see AF_ aliases. 540 | type: Socket Type see SOCK_ aliases. 541 | protocol: The protocol to use. 542 | Returns: A File Descriptor or -1 in case of failure. 543 | """ 544 | return external_call["socket", c_int, c_int, c_int, c_int](domain, type, protocol) # FnName, RetType # Args 545 | 546 | 547 | fn setsockopt( 548 | socket: c_int, 549 | level: c_int, 550 | option_name: c_int, 551 | option_value: DTypePointer[DType.uint8], 552 | option_len: socklen_t, 553 | ) -> c_int: 554 | """Libc POSIX `setsockopt` function 555 | Reference: https://man7.org/linux/man-pages/man3/setsockopt.3p.html 556 | Fn signature: int setsockopt(int socket, int level, int option_name, const void *option_value, socklen_t option_len). 557 | 558 | Args: 559 | socket: A File Descriptor. 560 | level: The protocol level. 561 | option_name: The option to set. 562 | option_value: A pointer to the value to set. 563 | option_len: The size of the value. 564 | Returns: 0 on success, -1 on error. 565 | """ 566 | return external_call[ 567 | "setsockopt", 568 | c_int, # FnName, RetType 569 | c_int, 570 | c_int, 571 | c_int, 572 | DTypePointer[DType.uint8], 573 | socklen_t, # Args 574 | ](socket, level, option_name, option_value, option_len) 575 | 576 | 577 | fn getsockopt( 578 | socket: c_int, 579 | level: c_int, 580 | option_name: c_int, 581 | option_value: DTypePointer[DType.uint8], 582 | option_len: UnsafePointer[socklen_t], 583 | ) -> c_int: 584 | """Libc POSIX `getsockopt` function 585 | Reference: https://man7.org/linux/man-pages/man3/getsockopt.3p.html 586 | Fn signature: int getsockopt(int socket, int level, int option_name, void *restrict option_value, socklen_t *restrict option_len). 587 | 588 | Args: socket: A File Descriptor. 589 | level: The protocol level. 590 | option_name: The option to get. 591 | option_value: A pointer to the value to get. 592 | option_len: DTypePointer to the size of the value. 593 | Returns: 0 on success, -1 on error. 594 | """ 595 | return external_call[ 596 | "getsockopt", 597 | c_int, # FnName, RetType 598 | c_int, 599 | c_int, 600 | c_int, 601 | DTypePointer[DType.uint8], 602 | UnsafePointer[socklen_t], # Args 603 | ](socket, level, option_name, option_value, option_len) 604 | 605 | 606 | fn getsockname( 607 | socket: c_int, 608 | address: UnsafePointer[sockaddr], 609 | address_len: UnsafePointer[socklen_t], 610 | ) -> c_int: 611 | """Libc POSIX `getsockname` function 612 | Reference: https://man7.org/linux/man-pages/man3/getsockname.3p.html 613 | Fn signature: int getsockname(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len). 614 | 615 | Args: socket: A File Descriptor. 616 | address: A pointer to a buffer to store the address of the peer. 617 | address_len: A pointer to the size of the buffer. 618 | Returns: 0 on success, -1 on error. 619 | """ 620 | return external_call[ 621 | "getsockname", 622 | c_int, # FnName, RetType 623 | c_int, 624 | UnsafePointer[sockaddr], 625 | UnsafePointer[socklen_t], # Args 626 | ](socket, address, address_len) 627 | 628 | 629 | fn getpeername( 630 | sockfd: c_int, 631 | addr: UnsafePointer[sockaddr], 632 | address_len: UnsafePointer[socklen_t], 633 | ) -> c_int: 634 | """Libc POSIX `getpeername` function 635 | Reference: https://man7.org/linux/man-pages/man2/getpeername.2.html 636 | Fn signature: int getpeername(int socket, struct sockaddr *restrict addr, socklen_t *restrict address_len). 637 | 638 | Args: sockfd: A File Descriptor. 639 | addr: A pointer to a buffer to store the address of the peer. 640 | address_len: A pointer to the size of the buffer. 641 | Returns: 0 on success, -1 on error. 642 | """ 643 | return external_call[ 644 | "getpeername", 645 | c_int, # FnName, RetType 646 | c_int, 647 | UnsafePointer[sockaddr], 648 | UnsafePointer[socklen_t], # Args 649 | ](sockfd, addr, address_len) 650 | 651 | 652 | fn bind(socket: c_int, address: UnsafePointer[sockaddr], address_len: socklen_t) -> c_int: 653 | """Libc POSIX `bind` function 654 | Reference: https://man7.org/linux/man-pages/man3/bind.3p.html 655 | Fn signature: int bind(int socket, const struct sockaddr *address, socklen_t address_len). 656 | """ 657 | return external_call["bind", c_int, c_int, UnsafePointer[sockaddr], socklen_t]( # FnName, RetType # Args 658 | socket, address, address_len 659 | ) 660 | 661 | 662 | fn listen(socket: c_int, backlog: c_int) -> c_int: 663 | """Libc POSIX `listen` function 664 | Reference: https://man7.org/linux/man-pages/man3/listen.3p.html 665 | Fn signature: int listen(int socket, int backlog). 666 | 667 | Args: socket: A File Descriptor. 668 | backlog: The maximum length of the queue of pending connections. 669 | Returns: 0 on success, -1 on error. 670 | """ 671 | return external_call["listen", c_int, c_int, c_int](socket, backlog) 672 | 673 | 674 | fn accept( 675 | socket: c_int, 676 | address: UnsafePointer[sockaddr], 677 | address_len: UnsafePointer[socklen_t], 678 | ) -> c_int: 679 | """Libc POSIX `accept` function 680 | Reference: https://man7.org/linux/man-pages/man3/accept.3p.html 681 | Fn signature: int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len). 682 | 683 | Args: socket: A File Descriptor. 684 | address: A pointer to a buffer to store the address of the peer. 685 | address_len: A pointer to the size of the buffer. 686 | Returns: A File Descriptor or -1 in case of failure. 687 | """ 688 | return external_call[ 689 | "accept", 690 | c_int, # FnName, RetType 691 | c_int, 692 | UnsafePointer[sockaddr], 693 | UnsafePointer[socklen_t], # Args 694 | ](socket, address, address_len) 695 | 696 | 697 | fn connect(socket: c_int, address: UnsafePointer[sockaddr], address_len: socklen_t) -> c_int: 698 | """Libc POSIX `connect` function 699 | Reference: https://man7.org/linux/man-pages/man3/connect.3p.html 700 | Fn signature: int connect(int socket, const struct sockaddr *address, socklen_t address_len). 701 | 702 | Args: socket: A File Descriptor. 703 | address: A pointer to the address to connect to. 704 | address_len: The size of the address. 705 | Returns: 0 on success, -1 on error. 706 | """ 707 | return external_call["connect", c_int, c_int, UnsafePointer[sockaddr], socklen_t]( # FnName, RetType # Args 708 | socket, address, address_len 709 | ) 710 | 711 | 712 | fn recv( 713 | socket: c_int, 714 | buffer: DTypePointer[DType.uint8], 715 | length: c_size_t, 716 | flags: c_int, 717 | ) -> c_ssize_t: 718 | """Libc POSIX `recv` function 719 | Reference: https://man7.org/linux/man-pages/man3/recv.3p.html 720 | Fn signature: ssize_t recv(int socket, void *buffer, size_t length, int flags). 721 | """ 722 | return external_call[ 723 | "recv", 724 | c_ssize_t, # FnName, RetType 725 | c_int, 726 | DTypePointer[DType.uint8], 727 | c_size_t, 728 | c_int, # Args 729 | ](socket, buffer, length, flags) 730 | 731 | 732 | fn send( 733 | socket: c_int, 734 | buffer: DTypePointer[DType.uint8], 735 | length: c_size_t, 736 | flags: c_int, 737 | ) -> c_ssize_t: 738 | """Libc POSIX `send` function 739 | Reference: https://man7.org/linux/man-pages/man3/send.3p.html 740 | Fn signature: ssize_t send(int socket, const void *buffer, size_t length, int flags). 741 | 742 | Args: socket: A File Descriptor. 743 | buffer: A pointer to the buffer to send. 744 | length: The size of the buffer. 745 | flags: Flags to control the behaviour of the function. 746 | Returns: The number of bytes sent or -1 in case of failure. 747 | """ 748 | return external_call[ 749 | "send", 750 | c_ssize_t, # FnName, RetType 751 | c_int, 752 | DTypePointer[DType.uint8], 753 | c_size_t, 754 | c_int, # Args 755 | ](socket, buffer, length, flags) 756 | 757 | 758 | fn shutdown(socket: c_int, how: c_int) -> c_int: 759 | """Libc POSIX `shutdown` function 760 | Reference: https://man7.org/linux/man-pages/man3/shutdown.3p.html 761 | Fn signature: int shutdown(int socket, int how). 762 | 763 | Args: socket: A File Descriptor. 764 | how: How to shutdown the socket. 765 | Returns: 0 on success, -1 on error. 766 | """ 767 | return external_call["shutdown", c_int, c_int, c_int](socket, how) # FnName, RetType # Args 768 | 769 | 770 | fn getaddrinfo( 771 | nodename: DTypePointer[DType.uint8], 772 | servname: DTypePointer[DType.uint8], 773 | hints: UnsafePointer[addrinfo], 774 | res: UnsafePointer[UnsafePointer[addrinfo]], 775 | ) -> c_int: 776 | """Libc POSIX `getaddrinfo` function 777 | Reference: https://man7.org/linux/man-pages/man3/getaddrinfo.3p.html 778 | Fn signature: int getaddrinfo(const char *restrict nodename, const char *restrict servname, const struct addrinfo *restrict hints, struct addrinfo **restrict res). 779 | """ 780 | return external_call[ 781 | "getaddrinfo", 782 | c_int, # FnName, RetType 783 | DTypePointer[DType.uint8], 784 | DTypePointer[DType.uint8], 785 | UnsafePointer[addrinfo], # Args 786 | UnsafePointer[UnsafePointer[addrinfo]], # Args 787 | ](nodename, servname, hints, res) 788 | 789 | 790 | fn getaddrinfo_unix( 791 | nodename: DTypePointer[DType.uint8], 792 | servname: DTypePointer[DType.uint8], 793 | hints: UnsafePointer[addrinfo_unix], 794 | res: UnsafePointer[UnsafePointer[addrinfo_unix]], 795 | ) -> c_int: 796 | """Libc POSIX `getaddrinfo` function 797 | Reference: https://man7.org/linux/man-pages/man3/getaddrinfo.3p.html 798 | Fn signature: int getaddrinfo(const char *restrict nodename, const char *restrict servname, const struct addrinfo *restrict hints, struct addrinfo **restrict res). 799 | """ 800 | return external_call[ 801 | "getaddrinfo", 802 | c_int, # FnName, RetType 803 | DTypePointer[DType.uint8], 804 | DTypePointer[DType.uint8], 805 | UnsafePointer[addrinfo_unix], # Args 806 | UnsafePointer[UnsafePointer[addrinfo_unix]], # Args 807 | ](nodename, servname, hints, res) 808 | 809 | 810 | fn gai_strerror(ecode: c_int) -> DTypePointer[DType.uint8]: 811 | """Libc POSIX `gai_strerror` function 812 | Reference: https://man7.org/linux/man-pages/man3/gai_strerror.3p.html 813 | Fn signature: const char *gai_strerror(int ecode). 814 | 815 | Args: ecode: The error code. 816 | Returns: A pointer to a string describing the error. 817 | """ 818 | return external_call["gai_strerror", DTypePointer[DType.uint8], c_int](ecode) # FnName, RetType # Args 819 | 820 | 821 | # fn inet_pton(address_family: Int, address: String) -> Int: 822 | # var ip_buf_size = 4 823 | # if address_family == AF_INET6: 824 | # ip_buf_size = 16 825 | 826 | # var ip_buf = DTypePointer[DType.uint8].alloc(ip_buf_size) 827 | # var conv_status = inet_pton(rebind[c_int](address_family), to_char_ptr(address), ip_buf) 828 | # return int(ip_buf.bitcast[c_uint]().load()) 829 | -------------------------------------------------------------------------------- /external/libc.mojo: -------------------------------------------------------------------------------- 1 | # C types 2 | alias c_void = UInt8 3 | alias c_char = UInt8 4 | alias c_schar = Int8 5 | alias c_uchar = UInt8 6 | alias c_short = Int16 7 | alias c_ushort = UInt16 8 | alias c_int = Int32 9 | alias c_uint = UInt32 10 | alias c_long = Int64 11 | alias c_ulong = UInt64 12 | alias c_float = Float32 13 | alias c_double = Float64 14 | 15 | # `Int` is known to be machine's width 16 | alias c_size_t = Int 17 | alias c_ssize_t = Int 18 | 19 | alias ptrdiff_t = Int64 20 | alias intptr_t = Int64 21 | alias uintptr_t = UInt64 22 | -------------------------------------------------------------------------------- /http_client/__init__.mojo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thatstoasty/mojo-http-client/b377403387e32825d6cc029fa7054ae1ff0876aa/http_client/__init__.mojo -------------------------------------------------------------------------------- /http_client/client.mojo: -------------------------------------------------------------------------------- 1 | import external.gojo.io 2 | from external.gojo.builtins import Byte 3 | from external.gojo.strings import StringBuilder 4 | from external.gojo.net.socket import Socket 5 | from external.gojo.net.ip import get_ip_address 6 | from .response import Response 7 | 8 | 9 | alias Headers = Dict[String, String] 10 | 11 | 12 | fn build_request_message( 13 | host: String, 14 | path: String, 15 | method: String, 16 | headers: Optional[Headers], 17 | data: Optional[Dict[String, String]] = None, 18 | ) -> String: 19 | var header = method.upper() + " " + path + " HTTP/1.1\r\n" 20 | header += "Host: " + host + "\r\n" 21 | 22 | if headers: 23 | var headers_mapping = headers.value()[] 24 | 25 | for pair in headers_mapping.items(): 26 | if pair[].key == "Connection": 27 | header += "Connection: " + pair[].value + "\r\n" 28 | elif pair[].key == "Content-Type": 29 | header += "Content-Type: " + pair[].value + "\r\n" 30 | elif pair[].key == "Content-Length": 31 | header += "Content-Length: " + pair[].value + "\r\n" 32 | else: 33 | header += pair[].key + ": " + pair[].value + "\r\n" 34 | else: 35 | # default to closing the connection so socket.receive() does not hang 36 | header += "Connection: close\r\n" 37 | 38 | # TODO: Only support dictionaries with string data for now 39 | if data: 40 | var data_string = stringify_data(data.value()[]) 41 | header += "Content-Length: " + String(len((data_string))) + "\r\n" 42 | header += "Content-Type: application/json\r\n" 43 | header += "\r\n" + data_string + "\r\n" 44 | 45 | header += "\r\n" 46 | return header 47 | 48 | 49 | fn stringify_data(data: Dict[String, String]) -> String: 50 | var key_count = data.size 51 | var builder = StringBuilder() 52 | _ = builder.write_string("{") 53 | 54 | var key_index = 0 55 | for pair in data.items(): 56 | _ = builder.write_string('"') 57 | _ = builder.write_string(pair[].key) 58 | _ = builder.write_string('"') 59 | _ = builder.write_string(':"') 60 | _ = builder.write_string(pair[].value) 61 | _ = builder.write_string('"') 62 | 63 | # Add comma for all elements except last 64 | if key_index != key_count - 1: 65 | _ = builder.write_string(",") 66 | key_index += 1 67 | 68 | _ = builder.write_string("}") 69 | return str(builder) 70 | 71 | 72 | @value 73 | struct HTTPClient: 74 | fn send_request( 75 | self, 76 | method: String, 77 | host: String, 78 | path: String, 79 | port: Int = 80, 80 | headers: Optional[Headers] = None, 81 | data: Optional[Dict[String, String]] = None, 82 | ) raises -> Response: 83 | var message = build_request_message(host, path, method, headers, data) 84 | print(message) 85 | var socket = Socket() 86 | 87 | # TODO: The message_len will break with unicode characters as they vary from 1-4 bytes. 88 | var message_len = len(message) 89 | var bytes_to_send = message.as_bytes() 90 | var bytes_sent = socket.send_to(bytes_to_send, get_ip_address(host), port) 91 | if bytes_sent != message_len: 92 | raise Error( 93 | "Failed to send the entire message. Bytes sent:" 94 | + str(bytes_sent) 95 | + " Message length:" 96 | + str(message_len) 97 | ) 98 | 99 | var bytes: List[UInt8] 100 | var err: Error 101 | bytes, err = io.read_all(socket) 102 | bytes.append(0) 103 | 104 | var response = Response(String(bytes)) 105 | socket.shutdown() 106 | err = socket.close() 107 | if err: 108 | raise err 109 | return response 110 | 111 | fn get( 112 | self, 113 | host: String, 114 | path: String, 115 | port: Int = 80, 116 | headers: Optional[Headers] = None, 117 | ) raises -> Response: 118 | return self.send_request("GET", host, path, port, headers=headers) 119 | 120 | fn post( 121 | self, 122 | host: String, 123 | path: String, 124 | port: Int = 80, 125 | headers: Optional[Headers] = None, 126 | data: Optional[Dict[String, String]] = None, 127 | ) raises -> Response: 128 | return self.send_request("POST", host, path, port, headers=headers, data=data) 129 | 130 | fn put( 131 | self, 132 | host: String, 133 | path: String, 134 | port: Int = 80, 135 | headers: Optional[Headers] = None, 136 | data: Optional[Dict[String, String]] = None, 137 | ) raises -> Response: 138 | return self.send_request("PUT", host, path, port, headers=headers, data=data) 139 | 140 | fn delete( 141 | self, 142 | host: String, 143 | path: String, 144 | port: Int = 80, 145 | headers: Optional[Headers] = None, 146 | ) raises -> Response: 147 | return self.send_request("DELETE", host, path, port, headers=headers) 148 | 149 | fn patch( 150 | self, 151 | host: String, 152 | path: String, 153 | port: Int = 80, 154 | headers: Optional[Headers] = None, 155 | data: Optional[Dict[String, String]] = None, 156 | ) raises -> Response: 157 | return self.send_request("PATCH", host, path, port, headers=headers, data=data) 158 | 159 | fn head( 160 | self, 161 | host: String, 162 | path: String, 163 | port: Int = 80, 164 | headers: Optional[Headers] = None, 165 | ) raises -> Response: 166 | return self.send_request("HEAD", host, path, port, headers=headers) 167 | 168 | fn options( 169 | self, 170 | host: String, 171 | path: String, 172 | port: Int = 80, 173 | headers: Optional[Headers] = None, 174 | ) raises -> Response: 175 | return self.send_request("DELETE", host, path, port, headers=headers) 176 | -------------------------------------------------------------------------------- /http_client/response.mojo: -------------------------------------------------------------------------------- 1 | from .client import Headers, stringify_data 2 | from external.gojo.fmt import sprintf 3 | 4 | 5 | fn split(input_string: String, sep: String = " ", owned maxsplit: Int = -1) -> List[String]: 6 | """The separator can be multiple characters long.""" 7 | var result = List[String]() 8 | if maxsplit == 0: 9 | result.append(input_string) 10 | return result 11 | if maxsplit < 0: 12 | maxsplit = len(input_string) 13 | 14 | if not sep: 15 | for i in range(len(input_string)): 16 | result.append(input_string[i]) 17 | 18 | return result 19 | 20 | var output = List[String]() 21 | var start = 0 22 | var split_count = 0 23 | 24 | for end in range(len(input_string) - len(sep) + 1): 25 | if input_string[end : end + len(sep)] == sep: 26 | output.append(input_string[start:end]) 27 | start = end + len(sep) 28 | split_count += 1 29 | 30 | if maxsplit > 0 and split_count >= maxsplit: 31 | break 32 | 33 | output.append(input_string[start:]) 34 | return output 35 | 36 | 37 | @value 38 | struct Response(Stringable): 39 | var original_message: String 40 | var scheme: String 41 | var protocol: String 42 | var status_code: Int 43 | var status_message: String 44 | var headers: Headers 45 | var body: String 46 | 47 | fn __init__(inout self): 48 | self.scheme = "" 49 | self.protocol = "" 50 | self.status_code = 0 51 | self.status_message = "" 52 | self.original_message = "" 53 | self.headers = Headers() 54 | self.body = "" 55 | 56 | fn __init__(inout self, response: String) raises: 57 | self.original_message = response 58 | 59 | # Split into status + headers and body. TODO: Only supports HTTP/1.1 Format for now 60 | var chunks = split(response, "\r\n\r\n", 1) 61 | var lines = chunks[0].split("\n") 62 | var status_line = lines[0].split(" ") 63 | 64 | var scheme_and_proto = status_line[0].split("/") 65 | self.scheme = scheme_and_proto[0].lower() 66 | self.protocol = status_line[0] 67 | self.status_code = atol(status_line[1]) 68 | self.status_message = status_line[2][:-1] 69 | 70 | self.headers = Headers() 71 | for i in range(1, len(lines), 1): 72 | var parts = lines[i].split(": ") 73 | if len(parts) == 2: 74 | self.headers[parts[0]] = parts[1][:-1] 75 | 76 | self.body = "" 77 | if len(chunks) > 1: 78 | self.body = chunks[1] 79 | 80 | fn __str__(self) -> String: 81 | return sprintf( 82 | "%s %d %s\n", 83 | self.protocol, 84 | self.status_code, 85 | self.status_message, 86 | ) 87 | -------------------------------------------------------------------------------- /http_client/uri.mojo: -------------------------------------------------------------------------------- 1 | from collections.dict import Dict 2 | from .client import String 3 | 4 | 5 | alias QueryParams = Dict[String, String] 6 | 7 | 8 | fn join(separator: String, iterable: List[String]) -> String: 9 | var result: String = "" 10 | for i in range(iterable.__len__()): 11 | result += iterable[i] 12 | if i != iterable.__len__() - 1: 13 | result += separator 14 | return result 15 | 16 | 17 | @value 18 | struct URI: 19 | var raw_host: String 20 | var host: String 21 | var scheme: String 22 | var path: String 23 | var _query_string: String 24 | 25 | var disable_path_normalization: Bool 26 | 27 | var full_uri: String 28 | var request_uri: String 29 | 30 | var username: String 31 | var password: String 32 | 33 | fn __init__( 34 | inout self, 35 | full_uri: String, 36 | ) raises -> None: 37 | self.raw_host = String() 38 | self.scheme = String() 39 | self.path = String() 40 | self._query_string = String() 41 | self.host = String() 42 | self.disable_path_normalization = False 43 | self.full_uri = full_uri 44 | self.request_uri = String() 45 | self.username = String() 46 | self.password = String() 47 | self.parse() 48 | 49 | fn __init__( 50 | inout self, 51 | scheme: String, 52 | host: String, 53 | path: String, 54 | ) -> None: 55 | self.raw_host = path 56 | self.scheme = scheme 57 | self.path = normalise_path(path, self.raw_host) 58 | self._query_string = String() 59 | self.host = host 60 | self.disable_path_normalization = False 61 | self.full_uri = String() 62 | self.request_uri = String() 63 | self.username = String() 64 | self.password = String() 65 | 66 | fn __init__( 67 | inout self, 68 | path_original: String, 69 | path: String, 70 | scheme: String, 71 | query_string: String, 72 | host: String, 73 | disable_path_normalization: Bool, 74 | full_uri: String, 75 | request_uri: String, 76 | username: String, 77 | password: String, 78 | ): 79 | self.raw_host = path_original 80 | self.scheme = scheme 81 | self.path = path 82 | self._query_string = query_string 83 | self.host = host 84 | self.disable_path_normalization = disable_path_normalization 85 | self.full_uri = full_uri 86 | self.request_uri = request_uri 87 | self.username = username 88 | self.password = password 89 | 90 | fn set_path(inout self, path: String) -> Self: 91 | self.path = normalise_path(path, self.raw_host) 92 | return self 93 | 94 | fn set_scheme(inout self, scheme: String) -> Self: 95 | self.scheme = scheme 96 | return self 97 | 98 | fn set_request_uri(inout self, request_uri: String) -> Self: 99 | self.request_uri = request_uri 100 | return self 101 | 102 | fn set_query_string(inout self, query_string: String) -> Self: 103 | self._query_string = query_string 104 | return self 105 | 106 | fn set_query_string(inout self, query_params: QueryParams) raises -> Self: 107 | var params = List[String]() 108 | for item in query_params.items(): 109 | params.append(item[].key + "=" + item[].value) 110 | 111 | self._query_string = join("&", params) 112 | return self 113 | 114 | fn set_host(inout self, host: String) -> Self: 115 | self.host = host 116 | return self 117 | 118 | fn parse(inout self) raises -> None: 119 | var raw_uri = String(self.full_uri) 120 | 121 | # Defaults to HTTP/1.1. TODO: Assume http for now, since nothing but http is supported. 122 | var proto_str: String = "HTTP/1.1" 123 | _ = self.set_scheme("http") 124 | 125 | # Parse requestURI 126 | var n = raw_uri.rfind(" ") 127 | # if n < 0: 128 | # n = len(raw_uri) 129 | # proto_str = "HTTP/1.0" 130 | # elif n == 0: 131 | # raise Error("Request URI cannot be empty") 132 | # else: 133 | # var proto = raw_uri[n + 1 :] 134 | # if proto != "HTTP/1.1": 135 | # proto_str = proto 136 | 137 | var request_uri = raw_uri[:n] 138 | 139 | # Parse host from requestURI 140 | # TODO: String null terminator issues are causing the last character of the host to be cut off. 141 | n = request_uri.find("://") 142 | if n >= 0: 143 | var host_and_port = request_uri[n + 3 :] 144 | n = host_and_port.find("/") 145 | if n >= 0: 146 | self.host = host_and_port[:n] 147 | request_uri = request_uri[n + 3 :] 148 | else: 149 | self.host = host_and_port 150 | request_uri = "/" 151 | else: 152 | n = request_uri.find("/") 153 | if n >= 0: 154 | self.host = request_uri[:n] 155 | request_uri = request_uri[n:] 156 | else: 157 | self.host = request_uri 158 | request_uri = "/" 159 | 160 | # Parse path 161 | n = request_uri.find("?") 162 | if n >= 0: 163 | self.raw_host = request_uri[:n] 164 | self._query_string = request_uri[n + 1 :] 165 | else: 166 | self.raw_host = request_uri 167 | self._query_string = String() 168 | 169 | self.path = normalise_path(self.raw_host, self.raw_host) 170 | 171 | _ = self.set_request_uri(request_uri) 172 | 173 | fn set_username(inout self, username: String) -> Self: 174 | self.username = username 175 | return self 176 | 177 | fn set_password(inout self, password: String) -> Self: 178 | self.password = password 179 | return self 180 | 181 | fn get_full_uri(self) -> String: 182 | var full_uri = self.scheme + "://" + self.host + self.path 183 | if len(self._query_string) > 0: 184 | full_uri += "?" + self._query_string 185 | return full_uri 186 | -------------------------------------------------------------------------------- /tests/__init__.mojo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thatstoasty/mojo-http-client/b377403387e32825d6cc029fa7054ae1ff0876aa/tests/__init__.mojo -------------------------------------------------------------------------------- /tests/test_client.mojo: -------------------------------------------------------------------------------- 1 | from tests.wrapper import MojoTest 2 | from http_client.client import HTTPClient, Headers 3 | 4 | 5 | fn test_post() raises: 6 | var test = MojoTest("Testing client.post") 7 | 8 | # Add headers 9 | var headers = Headers() 10 | headers["Connection"] = "close" 11 | 12 | # Add data 13 | var data = Dict[String, String]() 14 | data["hello"] = "world" 15 | 16 | var response = HTTPClient().post("www.httpbin.org", "/post", headers=headers, data=data) 17 | test.assert_equal(response.status_code, 200) 18 | test.assert_equal(response.status_message, "OK") 19 | test.assert_equal(response.headers["Content-Type"], "application/json") 20 | test.assert_equal(response.scheme, "http") 21 | 22 | 23 | # Simple GET request 24 | fn test_get() raises: 25 | var test = MojoTest("Testing client.get") 26 | var response = HTTPClient().get("www.example.com", "/", 80) 27 | print(response) 28 | test.assert_equal(response.status_code, 200) 29 | test.assert_equal(response.status_message, "OK") 30 | test.assert_equal(response.scheme, "http") 31 | 32 | 33 | # # TODO: Throwing malloc error, fix this test 34 | # # GET request with headers and query params. TODO: Throwing 405 for now, need to find an endpoint that accepts query params 35 | # fn test_query_params() raises: 36 | # var test = MojoTest("Testing query params") 37 | # var query_params = QueryParams() 38 | # query_params["world"] = "hello" 39 | # query_params["foo"] = "bar" 40 | 41 | # var uri = URI("http://www.httpbin.org") 42 | # _ = uri.set_query_string(query_params) 43 | 44 | # var client = HTTPClient("www.httpbin.org", 80) 45 | # var response = client.get("/get") 46 | # test.assert_equal(response.status_code, 405) 47 | # # TODO: Printing response.status_message shows the correct message, but it shows up as None in the assert?? 48 | # # testing.assert_equal(response.status_message, "OK") 49 | # test.assert_equal(response.scheme, "http") 50 | 51 | 52 | fn main() raises: 53 | test_get() 54 | test_post() 55 | # test_query_params() 56 | -------------------------------------------------------------------------------- /tests/test_uri.mojo: -------------------------------------------------------------------------------- 1 | from tests.wrapper import MojoTest 2 | from http_client.uri import QueryParams, URI 3 | 4 | 5 | fn test_uri() raises: 6 | var test = MojoTest("Testing URI") 7 | var uri = URI("http", "www.example.com", "/") 8 | var query_params = QueryParams() 9 | query_params["test"] = "param" 10 | query_params["test2"] = "param2" 11 | _ = uri.set_query_string(query_params) 12 | 13 | test.assert_equal(uri.scheme, "http") 14 | test.assert_equal(uri.host, "www.example.com") 15 | test.assert_equal(uri.path, "/") 16 | test.assert_equal(uri.get_full_uri(), "http://www.example.com/?test=param&test2=param2") 17 | test.assert_equal(uri._query_string, "test=param&test2=param2") 18 | 19 | # Should also work by passing in the full path 20 | var uri2 = URI("http://www.example.com/") 21 | _ = uri2.set_query_string(query_params) 22 | test.assert_equal(uri2.scheme, "http") 23 | test.assert_equal(uri2.host, "www.example.com") 24 | test.assert_equal(uri2.path, "/") 25 | test.assert_equal(uri2.get_full_uri(), "http://www.example.com/?test=param&test2=param2") 26 | test.assert_equal(uri2._query_string, "test=param&test2=param2") 27 | 28 | 29 | fn main() raises: 30 | test_uri() -------------------------------------------------------------------------------- /tests/wrapper.mojo: -------------------------------------------------------------------------------- 1 | from testing import testing 2 | 3 | 4 | @value 5 | struct MojoTest: 6 | """ 7 | A utility struct for testing. 8 | """ 9 | 10 | var test_name: String 11 | 12 | fn __init__(inout self, test_name: String): 13 | self.test_name = test_name 14 | print("# " + test_name) 15 | 16 | fn assert_true(self, cond: Bool, message: String = ""): 17 | try: 18 | if message == "": 19 | testing.assert_true(cond) 20 | else: 21 | testing.assert_true(cond, message) 22 | except e: 23 | print(e) 24 | 25 | fn assert_false(self, cond: Bool, message: String = ""): 26 | try: 27 | if message == "": 28 | testing.assert_false(cond) 29 | else: 30 | testing.assert_false(cond, message) 31 | except e: 32 | print(e) 33 | 34 | fn assert_equal[T: testing.Testable](self, left: T, right: T): 35 | try: 36 | testing.assert_equal(left, right) 37 | except e: 38 | print(e) 39 | --------------------------------------------------------------------------------