H2o: H2O.next.call(env)์˜ ๋ฒ„๊ทธ

์— ๋งŒ๋“  2020๋…„ 01์›” 20์ผ  ยท  7์ฝ”๋ฉ˜ํŠธ  ยท  ์ถœ์ฒ˜: h2o/h2o

์ด ๋ฌธ์ œ๋ฅผ "๋ฒ„๊ทธ" ๋ณด๊ณ ์„œ๋กœ ๋‹ค์‹œ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด์ œ ์ตœ์‹  ์†Œ์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Fedora์—์„œ๋„ ํ…Œ์ŠคํŠธํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

์ด ๋ฒ„๊ทธ์˜ ๋ฐ๋ชจ๋ฅผ ์œ„ํ•ด ๋‹ค์Œ h2o.conf๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

user: www
access-log: /var/log/h2o/h2o-access.log
error-log: /var/log/h2o/h2o-error.log
listen: 8080
hosts:
  "<my-ip-address-here>:8080":
    paths:
      "/dir0":
        mruby.handler: |
          proc {|env|
            resp = H2O.next.call(env)
            resp
          }
        proxy.reverse.url: "https://www.google.com" # this URL is just for testing/demonstration
      "/dir1":
        file.dir: "/usr/local/www/data/testh2o/dir1"
      "/dir2":
        file.dir: "/usr/local/www/data/testh2o/dir2"
      "/":
        file.dir: "/usr/local/www/data/testh2o"

์ด๊ฒƒ์€ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค: dir1, dir2 ๋ฐ dir3 ์•„๋ž˜์˜ ํŒŒ์ผ์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  http://<my-ip-address-here>:8080/dir0 ์š”์ฒญํ•˜๋ฉด Google ํ™ˆํŽ˜์ด์ง€๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค(๋ฐ๋ชจ์šฉ).

๊ทธ๋Ÿฌ๋‚˜ /path ์„ธ๊ทธ๋จผํŠธ๋ฅผ ํ•˜๋‚˜ ๋” ์ถ”๊ฐ€ํ•˜๋ฉด HardenedBSD, FreeBSD์— ๋Œ€ํ•ด ์—ฌ๊ธฐ ์—

๊นจ์ง„ h2o.conf:

user: www
access-log: /var/log/h2o/h2o-access.log
error-log: /var/log/h2o/h2o-error.log
listen: 8080
hosts:
  "<my-ip-address-here>:8080":
    paths:
      "/dir0":
        mruby.handler: |
          proc {|env|
            resp = H2O.next.call(env)
            # In my production conf I have here code to look for a certain header field.
            # If it finds it, this handler makes a http_request() to a certain URL on another server 
            # in order to trigger emptying a cache. 
            # This bug demo is independent of this code.
            resp
          }
        proxy.reverse.url: "https://www.google.com" # this URL is just for testing/demonstration
      "/dir1":
        file.dir: "/usr/local/www/data/testh2o/dir1"
      "/dir2":
        file.dir: "/usr/local/www/data/testh2o/dir2"
      "/dir3":
        file.dir: "/usr/local/www/data/testh2o/dir3"
      "/":
        file.dir: "/usr/local/www/data/testh2o"

์ด์ œ ๋‚ด๋ถ€ ์„œ๋ฒ„ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  Fedora์˜ ์˜ค๋ฅ˜ ๋กœ๊ทธ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋งํ•ฉ๋‹ˆ๋‹ค.

[h2o_mruby] in request:/dir0:mruby raised: (eval):28: can't modify `SCRIPT_NAME` with `H2O.next`. Is `H2O.reprocess` what you want? (RuntimeError)

์ด๊ฒƒ์€ b9989220bea9bda30f69083b66166c4c657bdf84 ์˜ ์†Œ์Šค์—์„œ ๋นŒ๋“œ๋œ H2O์™€ ํ•จ๊ป˜ ์ƒˆ๋กœ์šด Fedora 31์—

๋ชจ๋“  7 ๋Œ“๊ธ€

H2O.reprocess ์‚ฌ์šฉ์„ ์ œ์•ˆํ•œ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€์— ๋”ฐ๋ผ ์ด์ œ ์ด ์˜ต์…˜์„ ์‹œ๋„ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋™์ผํ•œ ๊ฒฐ๊ณผ: ๋” ๋งŽ์€ ๊ฒฝ๋กœ ์„ธ๊ทธ๋จผํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด h2o๊ฐ€ ์ถฉ๋Œํ•ฉ๋‹ˆ๋‹ค.

hosts:
  test.local:
    listen:
      port: 80
      host: 10.0.0.10
    paths:
      "/dir0":
        file.dir: "/usr/local/www/data/testh2o/dir0"
      "/dir1":
        file.dir: "/usr/local/www/data/testh2o/dir1"
      "/wp-admin":
        mruby.handler: |
          proc {|env|
            env['SCRIPT_NAME'] = '/proxy-wp-admin'
            resp = H2O.reprocess.call(env)
            resp
          }
      "/proxy-wp-admin":
        proxy.reverse.url: "https://www.google.com"

์ด ๊ตฌ์„ฑ์œผ๋กœ ๋ธŒ๋ผ์šฐ์ €์—์„œ http://test.local/wp-admin ๋ฅผ ์—ด๋ฉด Google ํ™ˆํŽ˜์ด์ง€๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ /dir2 ์™€ ๊ฐ™์ด ๊ฒฝ๋กœ ์„ธ๊ทธ๋จผํŠธ๋ฅผ ํ•˜๋‚˜ ๋” ์ถ”๊ฐ€ํ•˜๋ฉด ๋‹ค์Œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

Feb  4 17:12:11 web2 h2o[34490]: received fatal signal 11
Feb  4 17:12:11 web2 h2o[34490]: [34493] 0x4cf6a0 <???> at /usr/local/bin/h2o
Feb  4 17:12:11 web2 h2o[34490]: [34493] 0x8018c5946 <pthread_sigmask+0x536> at /lib/libthr.so.3
Feb  4 17:12:11 web2 h2o[34490]: [34493] 0x8018c4eb2 <pthread_getspecific+0xe12> at /lib/libthr.so.3

ํ—ˆ์šฉ๋˜๋Š” ๊ฒฝ๋กœ ์„ธ๊ทธ๋จผํŠธ์˜ ์ˆ˜์— ์‹ค์ œ๋กœ ๊ทธ๋Ÿฌํ•œ ๋‚ฎ์€ ์ œํ•œ์ด ์žˆ์Šต๋‹ˆ๊นŒ??? ์ด๊ฒƒ์€ ๋ฒ„๊ทธ์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. H2O.next ์™€ ๊ฐ™์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

H2O.next ๋ฐ H20.reprocess ๋‘˜ ๋‹ค ๊ณ ์žฅ๋‚œ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด๋ฏ€๋กœ ๋‹ค๋ฅธ ์†”๋ฃจ์…˜์„ ์ฐพ์•„์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค. @kazuho ์˜ ์˜ˆ์ œ๋ฅผ ๋”ฐ๋ผ mruby ์—์„œ ์—ญ๋ฐฉํ–ฅ ํ”„๋ก์‹œ ์ฒ˜๋ฆฌ๊ธฐ๋ฅผ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.

๋‚ด ํ…Œ์ŠคํŠธ ์„œ๋ฒ„์—์„œ ์ด๊ฒƒ์€ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ๊ณง ํ”„๋กœ๋•์…˜์— ๋ฐฐํฌํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์œ„์—์„œ ์–ธ๊ธ‰ํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ ๋™์ผํ•œ ๋ฌธ์ œ๋ฅผ ๊ฒช๊ณ  ์žˆ๋Š” ๋‹ค๋ฅธ ์‚ฌ๋žŒ์—๊ฒŒ ๋„์›€์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

proc {|env|
    # copy headers
    headers = {}
    env.each do |key, value|
      if /^HTTP_/.match(key)
        headers[$'] = value
      end
    end
    if env['CONTENT_TYPE']
        headers['CONTENT_TYPE'] = env['CONTENT_TYPE']
    end

    # issue the request
    input = env["rack.input"] ? env["rack.input"] : ""
    if env['QUERY_STRING'].to_s != ''
        uri = env['SCRIPT_NAME'] + env['PATH_INFO'] + '?' + env['QUERY_STRING']
    else
        uri = env['SCRIPT_NAME'] + env['PATH_INFO']
    end
    req = http_request(
      "http://<backend-ip>#{uri}",
      method:  env["REQUEST_METHOD"],
      headers: headers,
      body: input,
    )

    # Extract status, headers and body, so that I can look into the headers
    status, headers, body = req.join

    # actual work done here, if a certain header is present
    [status, headers, body]
}

์•„์ง ์ž‘๋™ํ•˜์ง€ ์•Š์ง€๋งŒ:
H2O ๋˜๋Š” mruby๋Š” ์›๋ž˜ CONTENT_TYPE ํ—ค๋”๋ฅผ HTTP_CONTENT_TYPE ๋กœ ๋ฐ”๊พธ์–ด WordPress ๋ฐฑ์—”๋“œ๋ฅผ ์ค‘๋‹จํ•ฉ๋‹ˆ๋‹ค...

์˜ˆ์‹œ:
"CONTENT_TYPE"=>"application/x-www-form-urlencoded; charset=UTF-8"
๋œ๋‹ค
"HTTP_CONTENT_TYPE"=>"application/x-www-form-urlencoded; charset=UTF-8"

CONTENT_TYPE ๋งŒ ์‚ฌ์šฉํ•˜๋„๋ก ๊ฐ•์ œํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๊นŒ?

๊ทธ๋ฆฌ๊ณ  ์ด ๋ชจ๋“  ๊ฒƒ์€ h2o.conf์— ๋‹ค๋ฅธ /path ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ์‹ถ์—ˆ๊ธฐ ๋•Œ๋ฌธ์—...

@utrenkner ๋Šฆ์–ด์„œ ์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. https://github.com/h2o/h2o/pull/2254 ์—์„œ ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋˜๊ธฐ๋ฅผ H2O.next ๋ฐ H2O.reprocess ๊ฐ€ ์ž˜ ์ž‘๋™ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

H2O ๋˜๋Š” mruby๋Š” ์›๋ณธ CONTENT_TYPE ํ—ค๋”๋ฅผ HTTP_CONTENT_TYPE์œผ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ WordPress ๋ฐฑ์—”๋“œ๋ฅผ ์ค‘๋‹จํ•ฉ๋‹ˆ๋‹ค...

๋™์ผํ•œ mruby ์ฒ˜๋ฆฌ๊ธฐ๋ฅผ ์‹œ๋„ํ–ˆ์ง€๋งŒ ์žฌํ˜„ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋‚ด ๋ง์€, ์ตœ์†Œํ•œ์˜ ์—…์ŠคํŠธ๋ฆผ ์„œ๋ฒ„( echo -ne "HTTP/1.1 200 OK\r\n\r\n" | nc -l 9000 )๋ฅผ ์ƒ์„ฑํ•˜๊ณ  h2o์—์„œ ๋ณด๋‚ธ ํ—ค๋”๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—๋Š” CONTENT_TYPE ์ด ์•„๋‹ˆ๋ผ HTTP_CONTENT_TYPE .
์ด HTTP_REQUEST ์ ‘๊ทผ์„ BTW, ๋‹น์‹ ์€ ํ•˜์ดํ”ˆ ํ—ค๋” ํ•ด์‹œ ํ‚ค์˜ ๋ณ€ํ™˜ ๋ฐ‘์ค„์— ์žˆ์Šต๋‹ˆ๋‹ค์ž…๋‹ˆ๋‹ค, ๋‹น์‹ ์€ ๋ณ€ํ™˜ํ•ด์•ผ HTTP_X_FOO ์— X-FOO ํ•˜์ง€ X_FOO . ์ด๊ฒƒ์ด ๊ทธ ๋ฌธ์ œ์˜ ์›์ธ์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ์„œ๋ฒ„ ๊ตฌํ˜„์€ ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ํŠน๋ณ„ํžˆ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜๋Š” content-type ํ—ค๋”๋กœ ์ธ์‹ํ•˜์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค.

@i110 ์ •๋ง ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค! ๋‚ด์ผ ํŒจ์น˜๋ฅผ ์‹œํ—˜ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์œ„์˜ mruby ํ•ธ๋“ค๋Ÿฌ๋ฅผ ํ…Œ์ŠคํŠธํ•ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์‹ค, ๋ฐ‘์ค„์„ ํ•˜์ดํ”ˆ์œผ๋กœ ๋ฐ”๊พธ๋Š” ๊ฒƒ์„ ์™„์ „ํžˆ ์žŠ์–ด ๋ฒ„๋ ธ์Šต๋‹ˆ๋‹ค. ์ด ๋ณ€๊ฒฝ์œผ๋กœ ์ธํ•ด ์˜๋„ํ•œ ๋Œ€๋กœ ์ž‘๋™ํ•˜๊ณ  CONTENT_TYPE ๋Š” CONTENT_TYPE !

@i110 ์ •๋ง ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค! #2254 ๋Š” H2O.next ๋ฐ H2O.reprocess ๋ฌธ์ œ๋ฅผ ๋ชจ๋‘ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค. ์‹ค์ œ๋กœ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด h2o.conf์— /path ์„ธ๊ทธ๋จผํŠธ๋ฅผ ๋” ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ, ๊ทธ๋ ‡์Šต๋‹ˆ๋‹ค!

์ด ํŽ˜์ด์ง€๊ฐ€ ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
0 / 5 - 0 ๋“ฑ๊ธ‰