Skip to content

Commit 2db1788

Browse files
committed
add main.scala
1 parent d6c4cea commit 2db1788

File tree

5 files changed

+217
-0
lines changed

5 files changed

+217
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
*.class
22
*.log
3+
.ensime*
34

45
# sbt specific
56
dist/*

build.sbt

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
name := "daewonHttp"
2+
3+
version := "1.0"
4+
5+
scalaVersion := "2.9.1"
6+
7+

project/plugins.sbt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
addSbtPlugin("org.ensime" % "ensime-sbt-cmd" % "0.1.0")

src/main/scala/common/main.scala

+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/**
2+
* la-scala static http server
3+
* 참고: http://twitter.github.com/scala_school/concurrency.html
4+
*/
5+
package daewon.http
6+
7+
import java.net._
8+
import java.util.concurrent._
9+
import java.util.Date
10+
import scala.io._
11+
import java.io._
12+
13+
/**
14+
* 서버 메인
15+
* Excutors 스레드 풀 사용
16+
*/
17+
class HttpServer(docRoot: String, port: Int) {
18+
val serverSocket = new ServerSocket(port)
19+
val pool: ExecutorService = Executors.newCachedThreadPool
20+
21+
def run() {
22+
try {
23+
while (true) {
24+
val socket = serverSocket.accept() // block here
25+
pool.execute(new Handler(docRoot, socket))
26+
}
27+
} finally {
28+
pool.shutdown()
29+
}
30+
}
31+
}
32+
33+
/**
34+
* Http 상수 모음
35+
*/
36+
object HttpConst {
37+
private val htmlExt = Set("html", "htm")
38+
val SP = " "
39+
val CRLF = "\r\n"
40+
val BODYDELIMITER = CRLF + CRLF
41+
42+
object Method {
43+
val GET = "GET"
44+
val POST = "POST"
45+
val DELETE = "DELETE"
46+
val PUT = "PUT"
47+
}
48+
49+
def isHTMLPage(path: String) = htmlExt(path.toLowerCase().split("\\.").last)
50+
}
51+
52+
/**
53+
* 응답 핸들러
54+
* 200 OK, 404 NotFound 에 대한 처리를 담당
55+
*/
56+
class Handler(docRoot: String, socket: Socket) extends Runnable {
57+
val iterator = io.Source.fromInputStream(socket.getInputStream)(Codec.UTF8).getLines()
58+
59+
def run() {
60+
try {
61+
if (iterator.isEmpty){
62+
log("cannot read input stream"); return
63+
}
64+
// 01. input stream으로부터 첫줄 읽어내서 메소드, 경로 등 파싱
65+
val Array(method, path, version) = iterator.next().split(HttpConst.SP)
66+
67+
log(method, path, version)
68+
69+
val file = new File(docRoot + path)
70+
val os = socket.getOutputStream
71+
72+
method match {
73+
case HttpConst.Method.GET => {
74+
if (!file.exists()) NotFound(os)
75+
else {
76+
if (HttpConst.isHTMLPage(path)) OK(file, os)
77+
else OKBinary(file, os)
78+
}
79+
}
80+
case _ => throw new Error("unsupported method ")
81+
}
82+
} finally {
83+
socket.close
84+
}
85+
}
86+
}
87+
88+
/*
89+
* 응답 모음
90+
* 응답 헤더 모음 쪽으로 리팩터링 필요
91+
*/
92+
abstract class Response {
93+
val BUF_SIZE = 1024 * 1024
94+
95+
/**
96+
* 날자는 매 응답마다 변경되어야 한다.
97+
*/
98+
def date = "Date: " + new Date().toString + HttpConst.CRLF
99+
100+
/**
101+
* 파일을 메모리에 들지 않기 위해 사용
102+
*/
103+
def writeFileAsStream(file: File, os: OutputStream){
104+
val fs = new FileInputStream(file)
105+
val buf: Array[Byte] = new Array[Byte](BUF_SIZE)
106+
107+
try {
108+
Stream.continually(fs.read(buf, 0, BUF_SIZE)).takeWhile(_ != -1).foreach( cnt => os.write(buf, 0, cnt))
109+
} catch{
110+
case e: IOException => log("can't write outputstream")
111+
} finally{
112+
fs.close
113+
}
114+
}
115+
}
116+
117+
/**
118+
* 404 에러 반환
119+
*/
120+
case class NotFound(os: OutputStream) extends Response {
121+
val head = """HTTP/1.1 404 Not Found
122+
Connection: Close""" + HttpConst.BODYDELIMITER
123+
124+
log(head)
125+
126+
os.write( (head + date) getBytes )
127+
}
128+
129+
/**
130+
* 200 OK with HTML 페이지
131+
*/
132+
case class OK(file: File, os: OutputStream) extends Response {
133+
val head = """HTTP/1.1 200 OK
134+
Connection: Close
135+
Content-Type: text/html; charset=utf-8
136+
""" + "Content-Length: " + file.length + HttpConst.BODYDELIMITER
137+
138+
log(head)
139+
140+
os.write(head getBytes)
141+
this.writeFileAsStream(file, os)
142+
}
143+
144+
/**
145+
* 200 OK 바이너리 (파일 다운로드)
146+
*/
147+
case class OKBinary(file: File, os: OutputStream) extends Response {
148+
val head = """HTTP/1.1 200 OK
149+
Connection: Close
150+
""" + "Content-type : application/octet-stream; " + file.getName + HttpConst.CRLF + "Content-Length: " + file.length + HttpConst.CRLF + "Content-Disposition: attachment; " + file.getName + HttpConst.BODYDELIMITER
151+
152+
log(head)
153+
154+
os.write(head getBytes)
155+
this.writeFileAsStream(file, os)
156+
}
157+
158+
/**
159+
* 추후 로그 변경을 위해서
160+
*/
161+
case class log(msg: String*) {
162+
println( msg.mkString(", ") )
163+
}
164+
165+
/**
166+
* Main
167+
*/
168+
object Main extends App {
169+
val docRoot = "."
170+
(new HttpServer(docRoot, 8080)).run
171+
}

src/main/scala/common/package.scala

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import java.io.File
2+
3+
package object common {
4+
/** `???` can be used for marking methods that remain to be implemented.
5+
* @throws An `Error`
6+
*/
7+
def ??? : Nothing = throw new Error("an implementation is missing")
8+
9+
type ??? = Nothing
10+
type *** = Any
11+
12+
13+
/**
14+
* Get a child of a file. For example,
15+
*
16+
* subFile(homeDir, "b", "c")
17+
*
18+
* corresponds to ~/b/c
19+
*/
20+
def subFile(file: File, children: String*) = {
21+
children.foldLeft(file)((file, child) => new File(file, child))
22+
}
23+
24+
/**
25+
* Get a resource from the `src/main/resources` directory. Eclipse does not copy
26+
* resources to the output directory, then the class loader cannot find them.
27+
*/
28+
def resourceAsStreamFromSrc(resourcePath: List[String]): Option[java.io.InputStream] = {
29+
val classesDir = new File(getClass.getResource(".").toURI)
30+
val projectDir = classesDir.getParentFile.getParentFile.getParentFile.getParentFile
31+
val resourceFile = subFile(projectDir, ("src" :: "main" :: "resources" :: resourcePath): _*)
32+
if (resourceFile.exists)
33+
Some(new java.io.FileInputStream(resourceFile))
34+
else
35+
None
36+
}
37+
}

0 commit comments

Comments
 (0)