Memra

Worked build: a source viewer that greps

◈ 6 cards

Assignment 1 #2 end to end: accept a URL or a bare IP address, configure the connection, check the status, decode with the server's charset, and print only the numbered lines that match a search string.

The deliverable

Two arguments in — a location and a search string — and a filtered, numbered listing out. Every piece of it is something you already have; the work is assembling them in an order that fails usefully.

Stage 1 — a URL, or a bare address

The first argument may be http://data.example.org:8080/comp348/week3.html or it may be 93.184.216.34. new URL("93.184.216.34") throws MalformedURLException — no scheme, no handler — so the fallback is to treat the argument as a host and build a URL around it. InetAddress.getByName(arg) accepts both a dotted-quad, which it parses without a lookup, and a hostname, which costs a DNS round trip and throws UnknownHostException. Try the URL first: it is the more specific interpretation, and only its failure means "maybe this was an address".

Stage 2 — configure before you connect

openConnection(), then a read timeout, a connect timeout, and a User-Agent. Java's default agent string is Java/ plus a version number, and a non-trivial number of sites treat that as a robot. Naming your program is honest and it makes the server's access log — the log you will be parsing in A1 #1 — legible.

Stage 3 — read the status, not just the body

Cast to HttpURLConnection and call getResponseCode() first. A 200 means read getInputStream(). Anything at 400 or above means getInputStream() will throw, so read getErrorStream() instead — and either report the failure or search the error page, but decide, rather than letting an exception decide for you. Reporting 404 Not Found is a useful message; reporting IOException for the same event is not.

Stage 4 — decode with the server's charset

The body is bytes. Pull charset= out of getContentType(), fall back explicitly to UTF-8, and wrap the stream: InputStreamReader(in, cs) inside a BufferedReader. This is the step that separates a tool from a toy — a page in Japanese or a page with a single accented name in it decodes correctly or it does not, and the platform default gives you a different answer on every machine.

Stage 5 — scan lines, not bytes

BufferedReader.readLine() returns one line without its terminator, and null at end of stream. Count lines as you go so a match can be reported with its number. Search the decoded string, never the byte stream: a byte-level search for socket misses nothing in ASCII but has no idea where a line ends, cannot report a line number, and breaks entirely the moment the page is UTF-16.

Worked example — the whole run

$ java Grep http://data.example.org:8080/comp348/week3.html socket
HTTP/1.1 200 OK  (text/html; charset=UTF-8)
  17: <li>Sockets for clients</li>
  42: <p>A socket is a connected endpoint that hands you two streams.</p>
2 matching lines of 118

And the failure path, which is the half that earns the marks:

$ java Grep http://data.example.org:8080/comp348/week9.html socket
HTTP/1.1 404 Not Found  (text/html; charset=UTF-8)
no such page — 812 bytes of error body ignored

Same code path until getResponseCode(), then a deliberate divergence. Notice what the program never does: print a stack trace and stop.

parseopenConnectionstreamreaderargumentURL or IPbuild URLfallbackconfiguretimeout, agentcheck statusgetResponseCodedecodeserver charsetscan linesprint matches
Read it as a checklist for the assignment, and note where the boundary falls: bytes become text at the decode stage, never after it. Each stage has exactly one failure it must handle — an unparseable argument, a dead host, a non-200 status, an unknown charset, and an empty result — and handling them is most of the difference between a working submission and a stack trace.
NORMAL ~/memra/learn/comp-348/source-viewer-grep utf-8 LF