Skip to content

Latest commit

History

38 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

How to Run the Project

# in one terminal
go run ./cmd/tcplistener | tee /tmp/requestline.txt
# in another one
curl http://localhost:42069/david/lee

test chucked encoding

The code creates a data pipeline where information flows from httpbin → your server → your client, without ever holding the entire response in memory at once.

# start the server in one terminal session
go run ./cmd/httpserver
# make a request in another sessionecho -e "GET /httpbin/html HTTP/1.1\r\nHost: localhost:42069\r\nConnection: close\r\n\r\n"| nc localhost 42069

Goroutines and Server Architecture

Why use goroutines?

go server.listen() in Serve():

This runs the server's listening loop in a background goroutine

Allows Serve() to return immediately rather than blocking

Makes it possible to start the server and still use the main thread for other tasks (like waiting for shutdown signals)

go s.handle(conn) in listen():

Creates a new goroutine for each incoming connection Enables the server to handle multiple connections concurrently Prevents one slow client from blocking others

Method Hierarchy

The methods have a clear hierarchy:

Serve(port): Top-level function

Creates the server instance Sets up the TCP listener Initializes the server state Starts the listening goroutine Returns control to caller

listen(): Mid-level method

Runs in its own goroutine Accepts new connections in a loop Spawns a handler goroutine for each new connection Continues accepting connections until server is stopped

handle(conn): Low-level method

Runs in its own goroutine for each client Processes a single HTTP request Generates the appropriate response Closes the connection when finished

Response helpers: Utility methods

writeSuccessResponse() writeErrorResponse()

Why Use a Goroutine Here?

You're right that the file must be read sequentially—we can't process data before reading it. The goroutine here doesn't mean things happen out of order; it means:

Benefits of Using a Goroutine

  • Non-blocking return: The getLinesChannel function returns immediately with a channel, rather than waiting for the entire file to be processed.
  • Concurrent processing: The main function can start consuming lines while the goroutine continues reading the file.
  • Decoupling: The producer (file reader) and consumer (line printer) can work at their own pace.

No Risk of Disordering

The data cannot be processed out of order because:

  • The goroutine reads the file sequentially, byte by byte.
  • It only sends complete lines to the channel when they're ready.
  • Channels in Go preserve the order of sent messages.
  • The main function processes the lines in the order they arrive on the channel.

What's Async Here?

There are two operations happening concurrently:

  1. Reading/parsing: The goroutine reads chunks from the file and parses them into lines.
  2. Processing/printing: The main function prints lines as they become available.

Benefits of This Pattern

  • Memory efficiency: You don't need to read the entire file into memory before processing.
  • Responsiveness: The program can start producing output before the entire input is read.
  • Throughput: In a more complex program, you could have multiple consumers processing the data.
  • Resource utilization: The CPU can work on printing while waiting for I/O operations.

Real-World Analogy

Think of it like an assembly line:

  • Worker 1 (goroutine): Reads chunks and assembles complete lines.
  • Conveyor belt (channel): Transports complete lines.
  • Worker 2 (main function): Takes lines and prints them.

Each worker does their job concurrently, but the processing order is maintained.

Use Sequential When:

funcgetLines(f io.ReadCloser) []string {
deferf.Close()
varlines []stringdata:=make([]byte, 8)
currentLine:=""for {
count, err:=f.Read(data)
iferr==io.EOF {
ifcurrentLine!="" {
lines=append(lines, currentLine)
}
break
}
iferr!=nil {
fmt.Fprintf(os.Stderr, "Error reading file: %v\n", err)
break
}
chunk:=string(data[:count])
parts:=strings.Split(chunk, "\n")
fori:=0; i<len(parts)-1; i++ {
currentLine+=parts[i]
lines=append(lines, currentLine)
currentLine=""
}
currentLine+=parts[len(parts)-1]
}
returnlines
}
funcmain() {
file, err:=os.Open("messages.txt")
iferr!=nil {
fmt.Fprintf(os.Stderr, "Error opening file: %v\n", err)
os.Exit(1)
}
lines:=getLines(file)
for_, line:=rangelines {
fmt.Printf("read: %s\n", line)
}
}

Files are small

Code simplicity is a priority Complete data is needed before processing can begin

Use Goroutines/Channels When:

Files are large

Processing can start with partial data You want to utilize IO waiting time You need to process streams (like network connections) You're building a pipeline of operations

For UDP

# in one terminal
go run ./cmd/udpsender
# in another terminal
nc -ul 42069

Reader Inside vs. Outside the Loop

// Option 1: Create reader each timefor {
line, err:=bufio.NewReader(os.Stdin).ReadString('\n')
// ...
}
// Option 2: Create reader oncereader:=bufio.NewReader(os.Stdin)
for {
line, err:=reader.ReadString('\n')
// ...
}

Memory allocation:

Option 1 creates a new reader on every loop iteration, which means:

  • New memory allocation each time
  • More work for garbage collection
  • Slightly higher CPU usage

Buffer reuse:

Option 2 reuses the same reader, which means:

The internal buffer gets reused Less memory churn More efficient

State:

The reader maintains internal state about its buffer. When created outside the loop:

  • It can use information from previous reads to optimize future reads
  • Better handling of partial reads (if a read doesn't consume all data)

About

a http parser from tcp

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - LordMoMA/httpfromtcp: a http parser from tcp · GitHub
Skip to content

Latest commit

History

38 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

How to Run the Project

# in one terminal
go run ./cmd/tcplistener | tee /tmp/requestline.txt
# in another one
curl http://localhost:42069/david/lee

test chucked encoding

The code creates a data pipeline where information flows from httpbin → your server → your client, without ever holding the entire response in memory at once.

# start the server in one terminal session
go run ./cmd/httpserver
# make a request in another sessionecho -e "GET /httpbin/html HTTP/1.1\r\nHost: localhost:42069\r\nConnection: close\r\n\r\n"| nc localhost 42069

Goroutines and Server Architecture

Why use goroutines?

go server.listen() in Serve():

This runs the server's listening loop in a background goroutine

Allows Serve() to return immediately rather than blocking

Makes it possible to start the server and still use the main thread for other tasks (like waiting for shutdown signals)

go s.handle(conn) in listen():

Creates a new goroutine for each incoming connection Enables the server to handle multiple connections concurrently Prevents one slow client from blocking others

Method Hierarchy

The methods have a clear hierarchy:

Serve(port): Top-level function

Creates the server instance Sets up the TCP listener Initializes the server state Starts the listening goroutine Returns control to caller

listen(): Mid-level method

Runs in its own goroutine Accepts new connections in a loop Spawns a handler goroutine for each new connection Continues accepting connections until server is stopped

handle(conn): Low-level method

Runs in its own goroutine for each client Processes a single HTTP request Generates the appropriate response Closes the connection when finished

Response helpers: Utility methods

writeSuccessResponse() writeErrorResponse()

Why Use a Goroutine Here?

You're right that the file must be read sequentially—we can't process data before reading it. The goroutine here doesn't mean things happen out of order; it means:

Benefits of Using a Goroutine

  • Non-blocking return: The getLinesChannel function returns immediately with a channel, rather than waiting for the entire file to be processed.
  • Concurrent processing: The main function can start consuming lines while the goroutine continues reading the file.
  • Decoupling: The producer (file reader) and consumer (line printer) can work at their own pace.

No Risk of Disordering

The data cannot be processed out of order because:

  • The goroutine reads the file sequentially, byte by byte.
  • It only sends complete lines to the channel when they're ready.
  • Channels in Go preserve the order of sent messages.
  • The main function processes the lines in the order they arrive on the channel.

What's Async Here?

There are two operations happening concurrently:

  1. Reading/parsing: The goroutine reads chunks from the file and parses them into lines.
  2. Processing/printing: The main function prints lines as they become available.

Benefits of This Pattern

  • Memory efficiency: You don't need to read the entire file into memory before processing.
  • Responsiveness: The program can start producing output before the entire input is read.
  • Throughput: In a more complex program, you could have multiple consumers processing the data.
  • Resource utilization: The CPU can work on printing while waiting for I/O operations.

Real-World Analogy

Think of it like an assembly line:

  • Worker 1 (goroutine): Reads chunks and assembles complete lines.
  • Conveyor belt (channel): Transports complete lines.
  • Worker 2 (main function): Takes lines and prints them.

Each worker does their job concurrently, but the processing order is maintained.

Use Sequential When:

funcgetLines(f io.ReadCloser) []string {
deferf.Close()
varlines []stringdata:=make([]byte, 8)
currentLine:=""for {
count, err:=f.Read(data)
iferr==io.EOF {
ifcurrentLine!="" {
lines=append(lines, currentLine)
}
break
}
iferr!=nil {
fmt.Fprintf(os.Stderr, "Error reading file: %v\n", err)
break
}
chunk:=string(data[:count])
parts:=strings.Split(chunk, "\n")
fori:=0; i<len(parts)-1; i++ {
currentLine+=parts[i]
lines=append(lines, currentLine)
currentLine=""
}
currentLine+=parts[len(parts)-1]
}
returnlines
}
funcmain() {
file, err:=os.Open("messages.txt")
iferr!=nil {
fmt.Fprintf(os.Stderr, "Error opening file: %v\n", err)
os.Exit(1)
}
lines:=getLines(file)
for_, line:=rangelines {
fmt.Printf("read: %s\n", line)
}
}

Files are small

Code simplicity is a priority Complete data is needed before processing can begin

Use Goroutines/Channels When:

Files are large

Processing can start with partial data You want to utilize IO waiting time You need to process streams (like network connections) You're building a pipeline of operations

For UDP

# in one terminal
go run ./cmd/udpsender
# in another terminal
nc -ul 42069

Reader Inside vs. Outside the Loop

// Option 1: Create reader each timefor {
line, err:=bufio.NewReader(os.Stdin).ReadString('\n')
// ...
}
// Option 2: Create reader oncereader:=bufio.NewReader(os.Stdin)
for {
line, err:=reader.ReadString('\n')
// ...
}

Memory allocation:

Option 1 creates a new reader on every loop iteration, which means:

  • New memory allocation each time
  • More work for garbage collection
  • Slightly higher CPU usage

Buffer reuse:

Option 2 reuses the same reader, which means:

The internal buffer gets reused Less memory churn More efficient

State:

The reader maintains internal state about its buffer. When created outside the loop:

  • It can use information from previous reads to optimize future reads
  • Better handling of partial reads (if a read doesn't consume all data)

About

a http parser from tcp

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - LordMoMA/httpfromtcp: a http parser from tcp · GitHub
Skip to content

Latest commit

History

38 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

How to Run the Project

# in one terminal
go run ./cmd/tcplistener | tee /tmp/requestline.txt
# in another one
curl http://localhost:42069/david/lee

test chucked encoding

The code creates a data pipeline where information flows from httpbin → your server → your client, without ever holding the entire response in memory at once.

# start the server in one terminal session
go run ./cmd/httpserver
# make a request in another sessionecho -e "GET /httpbin/html HTTP/1.1\r\nHost: localhost:42069\r\nConnection: close\r\n\r\n"| nc localhost 42069

Goroutines and Server Architecture

Why use goroutines?

go server.listen() in Serve():

This runs the server's listening loop in a background goroutine

Allows Serve() to return immediately rather than blocking

Makes it possible to start the server and still use the main thread for other tasks (like waiting for shutdown signals)

go s.handle(conn) in listen():

Creates a new goroutine for each incoming connection Enables the server to handle multiple connections concurrently Prevents one slow client from blocking others

Method Hierarchy

The methods have a clear hierarchy:

Serve(port): Top-level function

Creates the server instance Sets up the TCP listener Initializes the server state Starts the listening goroutine Returns control to caller

listen(): Mid-level method

Runs in its own goroutine Accepts new connections in a loop Spawns a handler goroutine for each new connection Continues accepting connections until server is stopped

handle(conn): Low-level method

Runs in its own goroutine for each client Processes a single HTTP request Generates the appropriate response Closes the connection when finished

Response helpers: Utility methods

writeSuccessResponse() writeErrorResponse()

Why Use a Goroutine Here?

You're right that the file must be read sequentially—we can't process data before reading it. The goroutine here doesn't mean things happen out of order; it means:

Benefits of Using a Goroutine

  • Non-blocking return: The getLinesChannel function returns immediately with a channel, rather than waiting for the entire file to be processed.
  • Concurrent processing: The main function can start consuming lines while the goroutine continues reading the file.
  • Decoupling: The producer (file reader) and consumer (line printer) can work at their own pace.

No Risk of Disordering

The data cannot be processed out of order because:

  • The goroutine reads the file sequentially, byte by byte.
  • It only sends complete lines to the channel when they're ready.
  • Channels in Go preserve the order of sent messages.
  • The main function processes the lines in the order they arrive on the channel.

What's Async Here?

There are two operations happening concurrently:

  1. Reading/parsing: The goroutine reads chunks from the file and parses them into lines.
  2. Processing/printing: The main function prints lines as they become available.

Benefits of This Pattern

  • Memory efficiency: You don't need to read the entire file into memory before processing.
  • Responsiveness: The program can start producing output before the entire input is read.
  • Throughput: In a more complex program, you could have multiple consumers processing the data.
  • Resource utilization: The CPU can work on printing while waiting for I/O operations.

Real-World Analogy

Think of it like an assembly line:

  • Worker 1 (goroutine): Reads chunks and assembles complete lines.
  • Conveyor belt (channel): Transports complete lines.
  • Worker 2 (main function): Takes lines and prints them.

Each worker does their job concurrently, but the processing order is maintained.

Use Sequential When:

funcgetLines(f io.ReadCloser) []string {
deferf.Close()
varlines []stringdata:=make([]byte, 8)
currentLine:=""for {
count, err:=f.Read(data)
iferr==io.EOF {
ifcurrentLine!="" {
lines=append(lines, currentLine)
}
break
}
iferr!=nil {
fmt.Fprintf(os.Stderr, "Error reading file: %v\n", err)
break
}
chunk:=string(data[:count])
parts:=strings.Split(chunk, "\n")
fori:=0; i<len(parts)-1; i++ {
currentLine+=parts[i]
lines=append(lines, currentLine)
currentLine=""
}
currentLine+=parts[len(parts)-1]
}
returnlines
}
funcmain() {
file, err:=os.Open("messages.txt")
iferr!=nil {
fmt.Fprintf(os.Stderr, "Error opening file: %v\n", err)
os.Exit(1)
}
lines:=getLines(file)
for_, line:=rangelines {
fmt.Printf("read: %s\n", line)
}
}

Files are small

Code simplicity is a priority Complete data is needed before processing can begin

Use Goroutines/Channels When:

Files are large

Processing can start with partial data You want to utilize IO waiting time You need to process streams (like network connections) You're building a pipeline of operations

For UDP

# in one terminal
go run ./cmd/udpsender
# in another terminal
nc -ul 42069

Reader Inside vs. Outside the Loop

// Option 1: Create reader each timefor {
line, err:=bufio.NewReader(os.Stdin).ReadString('\n')
// ...
}
// Option 2: Create reader oncereader:=bufio.NewReader(os.Stdin)
for {
line, err:=reader.ReadString('\n')
// ...
}

Memory allocation:

Option 1 creates a new reader on every loop iteration, which means:

  • New memory allocation each time
  • More work for garbage collection
  • Slightly higher CPU usage

Buffer reuse:

Option 2 reuses the same reader, which means:

The internal buffer gets reused Less memory churn More efficient

State:

The reader maintains internal state about its buffer. When created outside the loop:

  • It can use information from previous reads to optimize future reads
  • Better handling of partial reads (if a read doesn't consume all data)

About

a http parser from tcp

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - LordMoMA/httpfromtcp: a http parser from tcp · GitHub
Skip to content

Latest commit

History

38 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

How to Run the Project

# in one terminal
go run ./cmd/tcplistener | tee /tmp/requestline.txt
# in another one
curl http://localhost:42069/david/lee

test chucked encoding

The code creates a data pipeline where information flows from httpbin → your server → your client, without ever holding the entire response in memory at once.

# start the server in one terminal session
go run ./cmd/httpserver
# make a request in another sessionecho -e "GET /httpbin/html HTTP/1.1\r\nHost: localhost:42069\r\nConnection: close\r\n\r\n"| nc localhost 42069

Goroutines and Server Architecture

Why use goroutines?

go server.listen() in Serve():

This runs the server's listening loop in a background goroutine

Allows Serve() to return immediately rather than blocking

Makes it possible to start the server and still use the main thread for other tasks (like waiting for shutdown signals)

go s.handle(conn) in listen():

Creates a new goroutine for each incoming connection Enables the server to handle multiple connections concurrently Prevents one slow client from blocking others

Method Hierarchy

The methods have a clear hierarchy:

Serve(port): Top-level function

Creates the server instance Sets up the TCP listener Initializes the server state Starts the listening goroutine Returns control to caller

listen(): Mid-level method

Runs in its own goroutine Accepts new connections in a loop Spawns a handler goroutine for each new connection Continues accepting connections until server is stopped

handle(conn): Low-level method

Runs in its own goroutine for each client Processes a single HTTP request Generates the appropriate response Closes the connection when finished

Response helpers: Utility methods

writeSuccessResponse() writeErrorResponse()

Why Use a Goroutine Here?

You're right that the file must be read sequentially—we can't process data before reading it. The goroutine here doesn't mean things happen out of order; it means:

Benefits of Using a Goroutine

  • Non-blocking return: The getLinesChannel function returns immediately with a channel, rather than waiting for the entire file to be processed.
  • Concurrent processing: The main function can start consuming lines while the goroutine continues reading the file.
  • Decoupling: The producer (file reader) and consumer (line printer) can work at their own pace.

No Risk of Disordering

The data cannot be processed out of order because:

  • The goroutine reads the file sequentially, byte by byte.
  • It only sends complete lines to the channel when they're ready.
  • Channels in Go preserve the order of sent messages.
  • The main function processes the lines in the order they arrive on the channel.

What's Async Here?

There are two operations happening concurrently:

  1. Reading/parsing: The goroutine reads chunks from the file and parses them into lines.
  2. Processing/printing: The main function prints lines as they become available.

Benefits of This Pattern

  • Memory efficiency: You don't need to read the entire file into memory before processing.
  • Responsiveness: The program can start producing output before the entire input is read.
  • Throughput: In a more complex program, you could have multiple consumers processing the data.
  • Resource utilization: The CPU can work on printing while waiting for I/O operations.

Real-World Analogy

Think of it like an assembly line:

  • Worker 1 (goroutine): Reads chunks and assembles complete lines.
  • Conveyor belt (channel): Transports complete lines.
  • Worker 2 (main function): Takes lines and prints them.

Each worker does their job concurrently, but the processing order is maintained.

Use Sequential When:

funcgetLines(f io.ReadCloser) []string {
deferf.Close()
varlines []stringdata:=make([]byte, 8)
currentLine:=""for {
count, err:=f.Read(data)
iferr==io.EOF {
ifcurrentLine!="" {
lines=append(lines, currentLine)
}
break
}
iferr!=nil {
fmt.Fprintf(os.Stderr, "Error reading file: %v\n", err)
break
}
chunk:=string(data[:count])
parts:=strings.Split(chunk, "\n")
fori:=0; i<len(parts)-1; i++ {
currentLine+=parts[i]
lines=append(lines, currentLine)
currentLine=""
}
currentLine+=parts[len(parts)-1]
}
returnlines
}
funcmain() {
file, err:=os.Open("messages.txt")
iferr!=nil {
fmt.Fprintf(os.Stderr, "Error opening file: %v\n", err)
os.Exit(1)
}
lines:=getLines(file)
for_, line:=rangelines {
fmt.Printf("read: %s\n", line)
}
}

Files are small

Code simplicity is a priority Complete data is needed before processing can begin

Use Goroutines/Channels When:

Files are large

Processing can start with partial data You want to utilize IO waiting time You need to process streams (like network connections) You're building a pipeline of operations

For UDP

# in one terminal
go run ./cmd/udpsender
# in another terminal
nc -ul 42069

Reader Inside vs. Outside the Loop

// Option 1: Create reader each timefor {
line, err:=bufio.NewReader(os.Stdin).ReadString('\n')
// ...
}
// Option 2: Create reader oncereader:=bufio.NewReader(os.Stdin)
for {
line, err:=reader.ReadString('\n')
// ...
}

Memory allocation:

Option 1 creates a new reader on every loop iteration, which means:

  • New memory allocation each time
  • More work for garbage collection
  • Slightly higher CPU usage

Buffer reuse:

Option 2 reuses the same reader, which means:

The internal buffer gets reused Less memory churn More efficient

State:

The reader maintains internal state about its buffer. When created outside the loop:

  • It can use information from previous reads to optimize future reads
  • Better handling of partial reads (if a read doesn't consume all data)

About

a http parser from tcp

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - LordMoMA/httpfromtcp: a http parser from tcp · GitHub
Skip to content

Latest commit

History

38 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

How to Run the Project

# in one terminal
go run ./cmd/tcplistener | tee /tmp/requestline.txt
# in another one
curl http://localhost:42069/david/lee

test chucked encoding

The code creates a data pipeline where information flows from httpbin → your server → your client, without ever holding the entire response in memory at once.

# start the server in one terminal session
go run ./cmd/httpserver
# make a request in another sessionecho -e "GET /httpbin/html HTTP/1.1\r\nHost: localhost:42069\r\nConnection: close\r\n\r\n"| nc localhost 42069

Goroutines and Server Architecture

Why use goroutines?

go server.listen() in Serve():

This runs the server's listening loop in a background goroutine

Allows Serve() to return immediately rather than blocking

Makes it possible to start the server and still use the main thread for other tasks (like waiting for shutdown signals)

go s.handle(conn) in listen():

Creates a new goroutine for each incoming connection Enables the server to handle multiple connections concurrently Prevents one slow client from blocking others

Method Hierarchy

The methods have a clear hierarchy:

Serve(port): Top-level function

Creates the server instance Sets up the TCP listener Initializes the server state Starts the listening goroutine Returns control to caller

listen(): Mid-level method

Runs in its own goroutine Accepts new connections in a loop Spawns a handler goroutine for each new connection Continues accepting connections until server is stopped

handle(conn): Low-level method

Runs in its own goroutine for each client Processes a single HTTP request Generates the appropriate response Closes the connection when finished

Response helpers: Utility methods

writeSuccessResponse() writeErrorResponse()

Why Use a Goroutine Here?

You're right that the file must be read sequentially—we can't process data before reading it. The goroutine here doesn't mean things happen out of order; it means:

Benefits of Using a Goroutine

  • Non-blocking return: The getLinesChannel function returns immediately with a channel, rather than waiting for the entire file to be processed.
  • Concurrent processing: The main function can start consuming lines while the goroutine continues reading the file.
  • Decoupling: The producer (file reader) and consumer (line printer) can work at their own pace.

No Risk of Disordering

The data cannot be processed out of order because:

  • The goroutine reads the file sequentially, byte by byte.
  • It only sends complete lines to the channel when they're ready.
  • Channels in Go preserve the order of sent messages.
  • The main function processes the lines in the order they arrive on the channel.

What's Async Here?

There are two operations happening concurrently:

  1. Reading/parsing: The goroutine reads chunks from the file and parses them into lines.
  2. Processing/printing: The main function prints lines as they become available.

Benefits of This Pattern

  • Memory efficiency: You don't need to read the entire file into memory before processing.
  • Responsiveness: The program can start producing output before the entire input is read.
  • Throughput: In a more complex program, you could have multiple consumers processing the data.
  • Resource utilization: The CPU can work on printing while waiting for I/O operations.

Real-World Analogy

Think of it like an assembly line:

  • Worker 1 (goroutine): Reads chunks and assembles complete lines.
  • Conveyor belt (channel): Transports complete lines.
  • Worker 2 (main function): Takes lines and prints them.

Each worker does their job concurrently, but the processing order is maintained.

Use Sequential When:

funcgetLines(f io.ReadCloser) []string {
deferf.Close()
varlines []stringdata:=make([]byte, 8)
currentLine:=""for {
count, err:=f.Read(data)
iferr==io.EOF {
ifcurrentLine!="" {
lines=append(lines, currentLine)
}
break
}
iferr!=nil {
fmt.Fprintf(os.Stderr, "Error reading file: %v\n", err)
break
}
chunk:=string(data[:count])
parts:=strings.Split(chunk, "\n")
fori:=0; i<len(parts)-1; i++ {
currentLine+=parts[i]
lines=append(lines, currentLine)
currentLine=""
}
currentLine+=parts[len(parts)-1]
}
returnlines
}
funcmain() {
file, err:=os.Open("messages.txt")
iferr!=nil {
fmt.Fprintf(os.Stderr, "Error opening file: %v\n", err)
os.Exit(1)
}
lines:=getLines(file)
for_, line:=rangelines {
fmt.Printf("read: %s\n", line)
}
}

Files are small

Code simplicity is a priority Complete data is needed before processing can begin

Use Goroutines/Channels When:

Files are large

Processing can start with partial data You want to utilize IO waiting time You need to process streams (like network connections) You're building a pipeline of operations

For UDP

# in one terminal
go run ./cmd/udpsender
# in another terminal
nc -ul 42069

Reader Inside vs. Outside the Loop

// Option 1: Create reader each timefor {
line, err:=bufio.NewReader(os.Stdin).ReadString('\n')
// ...
}
// Option 2: Create reader oncereader:=bufio.NewReader(os.Stdin)
for {
line, err:=reader.ReadString('\n')
// ...
}

Memory allocation:

Option 1 creates a new reader on every loop iteration, which means:

  • New memory allocation each time
  • More work for garbage collection
  • Slightly higher CPU usage

Buffer reuse:

Option 2 reuses the same reader, which means:

The internal buffer gets reused Less memory churn More efficient

State:

The reader maintains internal state about its buffer. When created outside the loop:

  • It can use information from previous reads to optimize future reads
  • Better handling of partial reads (if a read doesn't consume all data)

About

a http parser from tcp

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - LordMoMA/httpfromtcp: a http parser from tcp · GitHub
Skip to content

Latest commit

History

38 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

How to Run the Project

# in one terminal
go run ./cmd/tcplistener | tee /tmp/requestline.txt
# in another one
curl http://localhost:42069/david/lee

test chucked encoding

The code creates a data pipeline where information flows from httpbin → your server → your client, without ever holding the entire response in memory at once.

# start the server in one terminal session
go run ./cmd/httpserver
# make a request in another sessionecho -e "GET /httpbin/html HTTP/1.1\r\nHost: localhost:42069\r\nConnection: close\r\n\r\n"| nc localhost 42069

Goroutines and Server Architecture

Why use goroutines?

go server.listen() in Serve():

This runs the server's listening loop in a background goroutine

Allows Serve() to return immediately rather than blocking

Makes it possible to start the server and still use the main thread for other tasks (like waiting for shutdown signals)

go s.handle(conn) in listen():

Creates a new goroutine for each incoming connection Enables the server to handle multiple connections concurrently Prevents one slow client from blocking others

Method Hierarchy

The methods have a clear hierarchy:

Serve(port): Top-level function

Creates the server instance Sets up the TCP listener Initializes the server state Starts the listening goroutine Returns control to caller

listen(): Mid-level method

Runs in its own goroutine Accepts new connections in a loop Spawns a handler goroutine for each new connection Continues accepting connections until server is stopped

handle(conn): Low-level method

Runs in its own goroutine for each client Processes a single HTTP request Generates the appropriate response Closes the connection when finished

Response helpers: Utility methods

writeSuccessResponse() writeErrorResponse()

Why Use a Goroutine Here?

You're right that the file must be read sequentially—we can't process data before reading it. The goroutine here doesn't mean things happen out of order; it means:

Benefits of Using a Goroutine

  • Non-blocking return: The getLinesChannel function returns immediately with a channel, rather than waiting for the entire file to be processed.
  • Concurrent processing: The main function can start consuming lines while the goroutine continues reading the file.
  • Decoupling: The producer (file reader) and consumer (line printer) can work at their own pace.

No Risk of Disordering

The data cannot be processed out of order because:

  • The goroutine reads the file sequentially, byte by byte.
  • It only sends complete lines to the channel when they're ready.
  • Channels in Go preserve the order of sent messages.
  • The main function processes the lines in the order they arrive on the channel.

What's Async Here?

There are two operations happening concurrently:

  1. Reading/parsing: The goroutine reads chunks from the file and parses them into lines.
  2. Processing/printing: The main function prints lines as they become available.

Benefits of This Pattern

  • Memory efficiency: You don't need to read the entire file into memory before processing.
  • Responsiveness: The program can start producing output before the entire input is read.
  • Throughput: In a more complex program, you could have multiple consumers processing the data.
  • Resource utilization: The CPU can work on printing while waiting for I/O operations.

Real-World Analogy

Think of it like an assembly line:

  • Worker 1 (goroutine): Reads chunks and assembles complete lines.
  • Conveyor belt (channel): Transports complete lines.
  • Worker 2 (main function): Takes lines and prints them.

Each worker does their job concurrently, but the processing order is maintained.

Use Sequential When:

funcgetLines(f io.ReadCloser) []string {
deferf.Close()
varlines []stringdata:=make([]byte, 8)
currentLine:=""for {
count, err:=f.Read(data)
iferr==io.EOF {
ifcurrentLine!="" {
lines=append(lines, currentLine)
}
break
}
iferr!=nil {
fmt.Fprintf(os.Stderr, "Error reading file: %v\n", err)
break
}
chunk:=string(data[:count])
parts:=strings.Split(chunk, "\n")
fori:=0; i<len(parts)-1; i++ {
currentLine+=parts[i]
lines=append(lines, currentLine)
currentLine=""
}
currentLine+=parts[len(parts)-1]
}
returnlines
}
funcmain() {
file, err:=os.Open("messages.txt")
iferr!=nil {
fmt.Fprintf(os.Stderr, "Error opening file: %v\n", err)
os.Exit(1)
}
lines:=getLines(file)
for_, line:=rangelines {
fmt.Printf("read: %s\n", line)
}
}

Files are small

Code simplicity is a priority Complete data is needed before processing can begin

Use Goroutines/Channels When:

Files are large

Processing can start with partial data You want to utilize IO waiting time You need to process streams (like network connections) You're building a pipeline of operations

For UDP

# in one terminal
go run ./cmd/udpsender
# in another terminal
nc -ul 42069

Reader Inside vs. Outside the Loop

// Option 1: Create reader each timefor {
line, err:=bufio.NewReader(os.Stdin).ReadString('\n')
// ...
}
// Option 2: Create reader oncereader:=bufio.NewReader(os.Stdin)
for {
line, err:=reader.ReadString('\n')
// ...
}

Memory allocation:

Option 1 creates a new reader on every loop iteration, which means:

  • New memory allocation each time
  • More work for garbage collection
  • Slightly higher CPU usage

Buffer reuse:

Option 2 reuses the same reader, which means:

The internal buffer gets reused Less memory churn More efficient

State:

The reader maintains internal state about its buffer. When created outside the loop:

  • It can use information from previous reads to optimize future reads
  • Better handling of partial reads (if a read doesn't consume all data)

About

a http parser from tcp

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - LordMoMA/httpfromtcp: a http parser from tcp · GitHub
Skip to content

Latest commit

History

38 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

How to Run the Project

# in one terminal
go run ./cmd/tcplistener | tee /tmp/requestline.txt
# in another one
curl http://localhost:42069/david/lee

test chucked encoding

The code creates a data pipeline where information flows from httpbin → your server → your client, without ever holding the entire response in memory at once.

# start the server in one terminal session
go run ./cmd/httpserver
# make a request in another sessionecho -e "GET /httpbin/html HTTP/1.1\r\nHost: localhost:42069\r\nConnection: close\r\n\r\n"| nc localhost 42069

Goroutines and Server Architecture

Why use goroutines?

go server.listen() in Serve():

This runs the server's listening loop in a background goroutine

Allows Serve() to return immediately rather than blocking

Makes it possible to start the server and still use the main thread for other tasks (like waiting for shutdown signals)

go s.handle(conn) in listen():

Creates a new goroutine for each incoming connection Enables the server to handle multiple connections concurrently Prevents one slow client from blocking others

Method Hierarchy

The methods have a clear hierarchy:

Serve(port): Top-level function

Creates the server instance Sets up the TCP listener Initializes the server state Starts the listening goroutine Returns control to caller

listen(): Mid-level method

Runs in its own goroutine Accepts new connections in a loop Spawns a handler goroutine for each new connection Continues accepting connections until server is stopped

handle(conn): Low-level method

Runs in its own goroutine for each client Processes a single HTTP request Generates the appropriate response Closes the connection when finished

Response helpers: Utility methods

writeSuccessResponse() writeErrorResponse()

Why Use a Goroutine Here?

You're right that the file must be read sequentially—we can't process data before reading it. The goroutine here doesn't mean things happen out of order; it means:

Benefits of Using a Goroutine

  • Non-blocking return: The getLinesChannel function returns immediately with a channel, rather than waiting for the entire file to be processed.
  • Concurrent processing: The main function can start consuming lines while the goroutine continues reading the file.
  • Decoupling: The producer (file reader) and consumer (line printer) can work at their own pace.

No Risk of Disordering

The data cannot be processed out of order because:

  • The goroutine reads the file sequentially, byte by byte.
  • It only sends complete lines to the channel when they're ready.
  • Channels in Go preserve the order of sent messages.
  • The main function processes the lines in the order they arrive on the channel.

What's Async Here?

There are two operations happening concurrently:

  1. Reading/parsing: The goroutine reads chunks from the file and parses them into lines.
  2. Processing/printing: The main function prints lines as they become available.

Benefits of This Pattern

  • Memory efficiency: You don't need to read the entire file into memory before processing.
  • Responsiveness: The program can start producing output before the entire input is read.
  • Throughput: In a more complex program, you could have multiple consumers processing the data.
  • Resource utilization: The CPU can work on printing while waiting for I/O operations.

Real-World Analogy

Think of it like an assembly line:

  • Worker 1 (goroutine): Reads chunks and assembles complete lines.
  • Conveyor belt (channel): Transports complete lines.
  • Worker 2 (main function): Takes lines and prints them.

Each worker does their job concurrently, but the processing order is maintained.

Use Sequential When:

funcgetLines(f io.ReadCloser) []string {
deferf.Close()
varlines []stringdata:=make([]byte, 8)
currentLine:=""for {
count, err:=f.Read(data)
iferr==io.EOF {
ifcurrentLine!="" {
lines=append(lines, currentLine)
}
break
}
iferr!=nil {
fmt.Fprintf(os.Stderr, "Error reading file: %v\n", err)
break
}
chunk:=string(data[:count])
parts:=strings.Split(chunk, "\n")
fori:=0; i<len(parts)-1; i++ {
currentLine+=parts[i]
lines=append(lines, currentLine)
currentLine=""
}
currentLine+=parts[len(parts)-1]
}
returnlines
}
funcmain() {
file, err:=os.Open("messages.txt")
iferr!=nil {
fmt.Fprintf(os.Stderr, "Error opening file: %v\n", err)
os.Exit(1)
}
lines:=getLines(file)
for_, line:=rangelines {
fmt.Printf("read: %s\n", line)
}
}

Files are small

Code simplicity is a priority Complete data is needed before processing can begin

Use Goroutines/Channels When:

Files are large

Processing can start with partial data You want to utilize IO waiting time You need to process streams (like network connections) You're building a pipeline of operations

For UDP

# in one terminal
go run ./cmd/udpsender
# in another terminal
nc -ul 42069

Reader Inside vs. Outside the Loop

// Option 1: Create reader each timefor {
line, err:=bufio.NewReader(os.Stdin).ReadString('\n')
// ...
}
// Option 2: Create reader oncereader:=bufio.NewReader(os.Stdin)
for {
line, err:=reader.ReadString('\n')
// ...
}

Memory allocation:

Option 1 creates a new reader on every loop iteration, which means:

  • New memory allocation each time
  • More work for garbage collection
  • Slightly higher CPU usage

Buffer reuse:

Option 2 reuses the same reader, which means:

The internal buffer gets reused Less memory churn More efficient

State:

The reader maintains internal state about its buffer. When created outside the loop:

  • It can use information from previous reads to optimize future reads
  • Better handling of partial reads (if a read doesn't consume all data)

About

a http parser from tcp

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - LordMoMA/httpfromtcp: a http parser from tcp · GitHub
Skip to content

Latest commit

History

38 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

How to Run the Project

# in one terminal
go run ./cmd/tcplistener | tee /tmp/requestline.txt
# in another one
curl http://localhost:42069/david/lee

test chucked encoding

The code creates a data pipeline where information flows from httpbin → your server → your client, without ever holding the entire response in memory at once.

# start the server in one terminal session
go run ./cmd/httpserver
# make a request in another sessionecho -e "GET /httpbin/html HTTP/1.1\r\nHost: localhost:42069\r\nConnection: close\r\n\r\n"| nc localhost 42069

Goroutines and Server Architecture

Why use goroutines?

go server.listen() in Serve():

This runs the server's listening loop in a background goroutine

Allows Serve() to return immediately rather than blocking

Makes it possible to start the server and still use the main thread for other tasks (like waiting for shutdown signals)

go s.handle(conn) in listen():

Creates a new goroutine for each incoming connection Enables the server to handle multiple connections concurrently Prevents one slow client from blocking others

Method Hierarchy

The methods have a clear hierarchy:

Serve(port): Top-level function

Creates the server instance Sets up the TCP listener Initializes the server state Starts the listening goroutine Returns control to caller

listen(): Mid-level method

Runs in its own goroutine Accepts new connections in a loop Spawns a handler goroutine for each new connection Continues accepting connections until server is stopped

handle(conn): Low-level method

Runs in its own goroutine for each client Processes a single HTTP request Generates the appropriate response Closes the connection when finished

Response helpers: Utility methods

writeSuccessResponse() writeErrorResponse()

Why Use a Goroutine Here?

You're right that the file must be read sequentially—we can't process data before reading it. The goroutine here doesn't mean things happen out of order; it means:

Benefits of Using a Goroutine

  • Non-blocking return: The getLinesChannel function returns immediately with a channel, rather than waiting for the entire file to be processed.
  • Concurrent processing: The main function can start consuming lines while the goroutine continues reading the file.
  • Decoupling: The producer (file reader) and consumer (line printer) can work at their own pace.

No Risk of Disordering

The data cannot be processed out of order because:

  • The goroutine reads the file sequentially, byte by byte.
  • It only sends complete lines to the channel when they're ready.
  • Channels in Go preserve the order of sent messages.
  • The main function processes the lines in the order they arrive on the channel.

What's Async Here?

There are two operations happening concurrently:

  1. Reading/parsing: The goroutine reads chunks from the file and parses them into lines.
  2. Processing/printing: The main function prints lines as they become available.

Benefits of This Pattern

  • Memory efficiency: You don't need to read the entire file into memory before processing.
  • Responsiveness: The program can start producing output before the entire input is read.
  • Throughput: In a more complex program, you could have multiple consumers processing the data.
  • Resource utilization: The CPU can work on printing while waiting for I/O operations.

Real-World Analogy

Think of it like an assembly line:

  • Worker 1 (goroutine): Reads chunks and assembles complete lines.
  • Conveyor belt (channel): Transports complete lines.
  • Worker 2 (main function): Takes lines and prints them.

Each worker does their job concurrently, but the processing order is maintained.

Use Sequential When:

funcgetLines(f io.ReadCloser) []string {
deferf.Close()
varlines []stringdata:=make([]byte, 8)
currentLine:=""for {
count, err:=f.Read(data)
iferr==io.EOF {
ifcurrentLine!="" {
lines=append(lines, currentLine)
}
break
}
iferr!=nil {
fmt.Fprintf(os.Stderr, "Error reading file: %v\n", err)
break
}
chunk:=string(data[:count])
parts:=strings.Split(chunk, "\n")
fori:=0; i<len(parts)-1; i++ {
currentLine+=parts[i]
lines=append(lines, currentLine)
currentLine=""
}
currentLine+=parts[len(parts)-1]
}
returnlines
}
funcmain() {
file, err:=os.Open("messages.txt")
iferr!=nil {
fmt.Fprintf(os.Stderr, "Error opening file: %v\n", err)
os.Exit(1)
}
lines:=getLines(file)
for_, line:=rangelines {
fmt.Printf("read: %s\n", line)
}
}

Files are small

Code simplicity is a priority Complete data is needed before processing can begin

Use Goroutines/Channels When:

Files are large

Processing can start with partial data You want to utilize IO waiting time You need to process streams (like network connections) You're building a pipeline of operations

For UDP

# in one terminal
go run ./cmd/udpsender
# in another terminal
nc -ul 42069

Reader Inside vs. Outside the Loop

// Option 1: Create reader each timefor {
line, err:=bufio.NewReader(os.Stdin).ReadString('\n')
// ...
}
// Option 2: Create reader oncereader:=bufio.NewReader(os.Stdin)
for {
line, err:=reader.ReadString('\n')
// ...
}

Memory allocation:

Option 1 creates a new reader on every loop iteration, which means:

  • New memory allocation each time
  • More work for garbage collection
  • Slightly higher CPU usage

Buffer reuse:

Option 2 reuses the same reader, which means:

The internal buffer gets reused Less memory churn More efficient

State:

The reader maintains internal state about its buffer. When created outside the loop:

  • It can use information from previous reads to optimize future reads
  • Better handling of partial reads (if a read doesn't consume all data)

About

a http parser from tcp

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages