2016年3月22日 星期二

Nginx 筆記 - 對於 requests 重導與 request_uri 變數 encode/decode 問題

事情是這樣的,有個服務應用配置 web server (www.localhost) 跟 app server (api.localhost) 時,有這這樣的特性:

網站首頁:http://www.localhost/
前端API:http://www.localhost/api/
真正API:http://api.localhost/

例如有一則 API 服務 http://www.localhost/api/helloworld 必須重導至 http://api.localhost/helloworld 才行。由於 web server 主要服務 static files 而 app server 則是服務 api ,但所有入口點都是在 web server,因此就需要將 requests 導向給後方的 app server:

upstream backend_hosts { server api.localhost:80; }

set $request_url $request_uri;
if ($uri ~ ^/api(.*)$ ) {
set $request_url $1;
}
location ^~ /api/ {
proxy_read_timeout 300;
proxy_set_header Host $http_host; proxy_redirect off;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://backend_hosts$request_url$is_args$args;
proxy_buffering off;
}


這樣,看似無限美好,但是有個關鍵要留意,上頭有做一次字串處理:set $request_url $1; 這一步把 /api/helloworld 轉成 /helloworld 沒錯,但 $request_url 變數連帶處理了解碼的問題,當整個 requests 只是常見的英數符號時,都是一切正常的,但如果是帶有中文字等需要做 URLEncode/Decode 時,就會出包啦。

$ sudo apt-get install nginx
$ sudo vim /etc/nginx/conf.d/www.conf
upstream backend_hosts { server 127.0.0.1:8000; }

# Web Server
server {
        listen 3000;

        set $request_url $request_uri;
        if ($uri ~ ^/api(.*)$ ) {
                set $request_url $1;
        }
        location ^~ /api/ {
                proxy_read_timeout 300;
                proxy_set_header Host $http_host; proxy_redirect off;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_pass http://backend_hosts$request_url$is_args$args;
                proxy_buffering off;
        }

        location ^~ / {
                add_header X-WWW-Request-URL $request_url;
                add_header X-WWW-Request-URI $request_uri;
                return 200;
        }
}
# APP Server
server {
        listen 8000;

        location ^~ / {
                add_header X-API-Request-URI $request_uri;
                return 200;
        }
}
$ sudo service nginx configtest && sudo service nginx restart


接著,使用 curl 來驗證:

$ curl -s -D - 'http://127.0.0.1:3000/'
HTTP/1.1 200 OK
Server: nginx/1.4.6 (Ubuntu)
Content-Type: application/octet-stream
Content-Length: 0
Connection: keep-alive
X-WWW-Request-URL: /
X-WWW-Request-URI: /


看見 X-WWW-Request-URL 和 X-WWW-Request-URI 代表為 3000 port 服務。

$ curl -s -D - 'http://127.0.0.1:3000/api/helloworld?abc=123'
HTTP/1.1 200 OK
Server: nginx/1.4.6 (Ubuntu)
Content-Type: application/octet-stream
Content-Length: 0
Connection: keep-alive
X-API-Request-URI: /helloworld?abc=123


可以看到 X-API-Request-URI: /helloworld?abc=123 時,代表有將 requests 導向到 8000 port 的服務。

接著,試試看URLEncode吧!

$ curl -s -D - 'http://127.0.0.1:3000/%E4%BD%A0%E5%A5%BD%E5%97%8E%0D%0A'          
HTTP/1.1 200 OK
Server: nginx/1.4.6 (Ubuntu)
Content-Type: application/octet-stream
Content-Length: 0
Connection: keep-alive
X-WWW-Request-URL: /%E4%BD%A0%E5%A5%BD%E5%97%8E%0D%0A
X-WWW-Request-URI: /%E4%BD%A0%E5%A5%BD%E5%97%8E%0D%0A


在 3000 port 服務中,X-WWW-Request-URL 和 X-WWW-Request-URI 一致是非常正常,但是,換成 /api/%E4%BD%A0%E5%A5%BD%E5%97%8E%0D%0A 就會出包啦:

$ curl -s -D - 'http://127.0.0.1:3000/api/%E4%BD%A0%E5%A5%BD%E5%97%8E%0D%0A'
HTTP/1.1 200 OK
Server: nginx/1.4.6 (Ubuntu)
Content-Type: application/octet-stream
Content-Length: 0
Connection: keep-alive
X-API-Request-URI: /%e4%bd%a0%e5%a5%bd%e5%97%8e%0d


仔細看會發現 X-API-Request-URI 缺少 %0A 的資料,並且看到 request_uri 有點像是被重新組合過,從原本的大寫變小寫了,但最重要的是有缺資料!

而解法?就是改用 rewrite 來避開了,但是對於這種 URLEncode 的,還必須有前置處理,例如定義新的服務網址: /api/urlencode/%E4%BD%A0%E5%A5%BD%E5%97%8E%0D%0A 用法,接著更新 nginx 設定檔:

upstream backend_hosts { server 127.0.0.1:8000; }

server {
        listen 3000;

        set $request_url $request_uri;

        if ($request_uri ~ ^/api(.*)$ ) {
                rewrite ^ $request_uri;
                rewrite ^/api(.*)$ $1;
                set  $request_url $1;
        }

        location ^~ / {

                add_header X-WWW-Request-URL $request_url;
                add_header X-WWW-Request-URI $request_uri;
                return 200;
        }

        location ^~ /urlencode {
                proxy_read_timeout 300;
                proxy_set_header Host $http_host; proxy_redirect off;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_pass http://backend_hosts$request_url;
                proxy_buffering off;
        }
}

server {
        listen 8000;

        location ^~ / {
                add_header X-API-Request-URI $request_uri;
                return 200;
        }
}


如此一來,就會看到 URLENCODE 資訊是沒有被解析過,完整的 pass 到 8000 port 服務囉

$ curl -s -D - 'http://127.0.0.1:3000/api/urlencode/%E4%BD%A0%E5%A5%BD%E5%97%8E%0D%0A'
HTTP/1.1 200 OK
Server: nginx/1.4.6 (Ubuntu)
Content-Type: application/octet-stream
Content-Length: 0
Connection: keep-alive
X-API-Request-URI: /urlencode/%E4%BD%A0%E5%A5%BD%E5%97%8E%0D%0A

2016年3月17日 星期四

[Linux] 透過 du + sort 指令,查詢資料儲存最大量的目錄 @ Ubuntu 14.04

還滿常用 du -hd1 的指令,可以列出當下目錄的使用情況,且只列出一層子目錄。然而,列完後又想要排序,以此更快抓到問題 :P 發現 sort -h 指令頗好用:-h, --human-numeric-sort, compare human readable numbers (e.g., 2K 1G)

所以連續技:

$ du -hd1
...
13M     ./Cache
552M    ./Uploads
340K    ./css
1GB     .


$ du -hd1 | sort -h
...
340K    ./css
13M     ./Cache
552M    ./Uploads
1GB     .

2016年3月15日 星期二

AWS 筆記 - 使用 CodeCommit

AWS_CodeCommit_05

雖然很習慣自己架 git server ,但基於摸熟 AWS 服務,就小試一下。整個流程很簡單,建立一個 git repo 就輸入完名稱即可建立,接著提供 SSH / HTTPS 的存取方式。而特別的地方應該是 Triggers ,可以搭配以下四種 event 觸發動作,如簡訊寫信或HTTP endpoint (AWS SNS) 或是呼叫 AWS Lambda function 做事:

AWS_CodeCommit_06

其中以 AWS SNS 為例,還必須先去 AWS SNS 建立的對應服務才行,也能 Test trigger 一下,看起來是把資料都埋在 Message 裡頭:

{
  "Type" : "Notification",
  "MessageId" : "580672ce-b036-5484-b1ca-3daf00303ba3",
  "TopicArn" : "arn:aws:sns:us-east-1:HelloWorld",
  "Subject" : "TEST: AWS CodeCommit us-east-1 push: study-codecommit",
  "Message" : "{\"Records\":[{\"awsRegion\":\"us-east-1\",\"codecommit\":{\"references\":[{\"commit\":\"0000000000000000000000000000000000000000\",\"ref\":\"refs/heads/TestReference\"}]},\"customData\":\"codecommit\",\"eventId\":\"36e338ce-a719-4188-93bb-aa6c28298a71\",\"eventName\":\"TriggerEventTest\",...}]}",
  ...
}

AWS 筆記 - 使用 Simple Notification Service (SNS)

AWS_SNS_01_home

共有 4種動作可挑選,以 Create Topic 為例,先輸入完 Topic 名稱後,可以在挑選 Subscription:

AWS_SNS_02_Subscription

以 HTTP/HTTPS 為例,就是會去 query 一個網址,而 SMS 則會寄簡訊。而 HTTP/HTTPS 也不是隨便設置完就好,還必須認證該 HTTP endpoint 是由你所擁有的,過程為:

Create Subscription -> HTTP/HTTPS -> 輸入 HTTP Endpoint -> Request confirmations -> 這時 AWS 就會發 HTTP POST 資料過去,請記得印出來,把 SubscribeURL 取出來用 -> Confirm Subscription 輸入剛剛的 SubscribeURL 即可完成驗證

這時在 HTTP endpoint 會收到 HTTP POST 資料,以 PHP 為例:

<?php

file_put_contents('/tmp/sns_check', file_get_contents('php://input'));


{
  "Type" : "SubscriptionConfirmation",
  "MessageId" : "9f5f6846-5227-4ef9-b94d-c172ef6c9c23",
  "Token" : "xxxxxx",
  "TopicArn" : "arn:aws:sns:us-east-1:HelloWorld",
  "Message" : "You have chosen to subscribe to the topic arn:aws:sns:us-east-1:HelloWorld.\nTo confirm the subscription, visit the SubscribeURL included in this message.",
  "SubscribeURL" : "https://sns.us-east-1.amazonaws.com/?Action=ConfirmSubscription&TopicArn=arn:aws:sns:us-east-1:HelloWorld&Token=xxxxx
",
  ...
}


若一直沒有去做驗證,會一直卡在 PendingConfirmation 狀態,而沒確認的將於三天後自動刪除。

最後,也可以在AWS SNS介面上發動通知事件(Publish a message),以 raw format 為例,填寫的資料會出現在 Message 中:

AWS_SNS_05_Publish_message

若輸入的 raw = yoyo 字眼,那 HTTP endpoint 收到的 HTTP Post 資料為:

{
  "Type" : "Notification",
  "MessageId" : "f737bee7-7567-5f29-b230-f186ddaa84cc",
  "TopicArn" : "arn:aws:sns:us-east-1:HelloWorld",
  "Subject" : "haha",
  "Message" : "yoyo",
   ...
  }
}

2016年3月12日 星期六

Android 開發筆記 - 使用 Android Studio 和 ViewPagerIndicator

上網亂找一下,大家好像還滿推四年前就沒再更新的 ViewPagerIndicator ,且很猛的共有超過 7k 的人關注,接近 4k 的人 fork 了此專案:https://github.com/JakeWharton/ViewPagerIndicator

總之,為了之後的維護管理,還是老樣子繼續練 Android Studio!添加額外第三方時,因為此 project 已經很久沒更新了,所以要採用下述安裝法:

build.gradle (Project):

allprojects {
repositories {
maven { url "http://dl.bintray.com/populov/maven" }
mavenCentral()
}
}


build.gradle (app):

dependencies {
// https://github.com/JakeWharton/ViewPagerIndicator/releases , 2012/09/13: 2.4.1
compile 'com.viewpagerindicator:library:2.4.1@aar'
// 預設的 Android Studio 環境應該不會使用下述,若仍有問題再開啟吧
//compile 'com.android.support:support-v4:23.2.0'
}


接著在 content_main.xml (若其他 layout) 添加 com.viewpagerindicator.* 系列程式碼,並且要配置 android.support.v4.view.ViewPager 才行:

    <com.viewpagerindicator.TabPageIndicator
        android:id="@+id/indicator"
        android:layout_height="wrap_content"
        android:layout_width="fill_parent"
        />
    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="fill_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        />


最後在 Activity 初始化:

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ViewPager mViewPager = (ViewPager)findViewById(R.id.pager);
        mViewPager.setAdapter(new PagerAdapter() {
            @Override
            public int getCount() {
                return 0;
            }

            @Override
            public boolean isViewFromObject(View view, Object object) {
                return false;
            }
        });

        TabPageIndicator mTabPageIndicator = (TabPageIndicator)findViewById(R.id.indicator);
        mTabPageIndicator.setViewPager(mViewPager);
}


以上是先滿足編譯跟執行需求,至於 TabPageIndicator 真的要怎樣用就不在此贅述了。

2016年3月9日 星期三

Bash 筆記 - 變數的字串取代方法(餵給 sed 關鍵字的前置處理)

這個需求是這樣的,想要用 sed 來取代檔案中的資料,但用 sed 查詢資料時,又需要處理 pattern 字串,例如想把檔案內所有 "http://127.0.0.1:8000" 的關鍵都取代成 "/"

這時用 sed 的指令就變成 sed 's/http:\/\/127\.0\.0\.1:8000/\//' filename,變得有點不方便,所以就想到有沒有在餵給 sed 指令前,把字串處理一下來避開 sed regular expression 的用法,找了一下,頗方便:

#!/bin/bash

keyword=$1

keyword =${keyword//\//\\\/};
keyword =${keyword//-/\\-};
keyword =${keyword//\./\\.};

cmd="sed 's/$keyword/\//' filename"

echo $cmd
eval $cmd


其中 Bash 裡的字串,要做取代的方式:

#只處理一次取代
keyword=${keyword/OldPattern/NewPattern}

#處理全部處代
keyword=${keyword//OldPattern/NewPattern}


收工!

2016年3月6日 星期日

Android 開發筆記 - 在 Android NDK/JNI 環境中,操作 HashMap 和 Vector 方法與資料型態定義

過去在 C++ 把玩 std::vector<std::unordered_map<int, std::string>> ,而搬到 Java 環境中時,就會出現檔案形態的轉變,於是乎就先挑個 Vector<HashMap<String,String>> 來試試,順手筆記一下。

其中 JNI 在呼叫 Java 物件時,Java 八大型態及 Java Class 型態在 JNI 中採用的關鍵字:

Java boolean type => "Z"
Java byte type => "B"
Java char type => "C"
Java short type => "S"
Java int type => "I"
Java long type => "J"
Java float type => "F"
Java double type => "D"
Java object type => "Ljava/lang/Object;"
Java Array of int type => "[I"
Java Array of double type => "[D"
Java Array of java/lang/Object type => "[Ljava/lang/Object;"


舉例來說,以 Vector 的 public boolean add(E e) 函數為例:

jclass vectorClass = env->FindClass("java.util.Vector");
jmethodID vector_add = env->GetMethodID(vectorClass, "add", "(Ljava/lang/Object;)Z");


其中 "(Ljava/lang/Object;)Z" 代表此函式回傳為 boolean 型態,而需要傳遞一個 java/lang/Object 當作參數。

最後是比較冗長的操作:

Java code:

static {
System.loadLibrary("test")
}
public native boolean test(Vector<HashMap<String, String>> obj);

void usage() {

Vector<HashMap<String, String>> output = new Vector<HashMap<String, String>>();
boolean ret = test(output);
Log.i(tag, "Ret: "+ret+", output size: "+output.size());
}


build.gradle (Module:app):

android.ndk {
moduleName = "test"
ldLibs.addAll(["log"])
CFlags.add("-std=c++11")
stl = "c++_shared"
}


C/C++ code:

#include <jni.h>
#include <android/log.h>

#define LOG_TAG "JNI"
#define LOGV(...)__android_log_print( ANDROID_LOG_VERBOSE,LOG_TAG, __VA_ARGS__ )
#define LOGD(...)__android_log_print( ANDROID_LOG_DEBUG,LOG_TAG, __VA_ARGS__ )
#define LOGI(...)__android_log_print( ANDROID_LOG_INFO,LOG_TAG, __VA_ARGS__ )
#define LOGW(...)__android_log_print( ANDROID_LOG_WARN,LOG_TAG, __VA_ARGS__ )
#define LOGE(...)__android_log_print( ANDROID_LOG_ERROR,LOG_TAG, __VA_ARGS__ )

extern "C" {


jboolean Java_YourPackageName_test(JNIEnv *env, jobject thiz, jobject vectorObject) {

jclass vectorClass = env->GetObjectClass(vectorObject);
jmethodID vector_size = env->GetMethodID(vectorClass, "size", "()I");
jmethodID vector_add = env->GetMethodID(vectorClass, "add", "(Ljava/lang/Object;)Z");
jmethodID vector_elementAt = env->GetMethodID(vectorClass, "elementAt", "(I)Ljava/lang/Object;");

jclass hashMapClass = env->FindClass("java/util/HashMap");
jmethodID hashMap_init = env->GetMethodID(hashMapClass, "<init>", "(I)V");
jmethodID hashMap_put = env->GetMethodID(hashMapClass, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
jmethodID hashMap_get = env->GetMethodID(hashMapClass, "get", "(Ljava/lang/Object;)Ljava/lang/Object;");
jmethodID hashMap_size = env->GetMethodID(hashMapClass, "size", "()I");

std::unordered_map<int, std::string> attrs;
attrs[0] = "Hello";
attrs[1] = "World";

if (vectorObject && vectorClass && hashMapClass) {
// init HashMap object
jobject dataHashMap = env->NewObject(hashMapClass, hashMap_init, 1);
for (auto it = attrs.begin(); it != attrs.end(); ++it) {
jstring key = env->NewStringUTF(std::to_string(it->first).c_str());
jstring value = env->NewStringUTF(it->second.c_str());
env->CallObjectMethod(dataHashMap, hashMap_put, key, value);
env->DeleteLocalRef(key);
env->DeleteLocalRef(value);
}
LOGI("hashmap size: %d", env->CallIntMethod(dataHashMap, hashMap_size));

LOGI("vector size: %d", env->CallIntMethod(vectorObject, vector_size));

// vector add
env->CallBooleanMethod(vectorObject, vector_add, dataHashMap);
env->DeleteLocalRef(dataHashMap);

// access vector
for (int i=0, cnt = env->CallIntMethod(vectorObject, vector_size) ; i<cnt; ++i) {
LOGI("get vector_elementAt: %d", i);
if (vector_elementAt) {
jobject dataInVector = env->CallObjectMethod(vectorObject, vector_elementAt, i);
jclass dataInVectorClass = env->GetObjectClass(dataInVector);
jmethodID dataInVector_get = env->GetMethodID(dataInVectorClass, "get", "(Ljava/lang/Object;)Ljava/lang/Object;");
if (dataInVector && dataInVector_get) {
jstring key = env->NewStringUTF(std::to_string(0).c_str());
jstring value = (jstring)env->CallObjectMethod(dataInVector, dataInVector_get, key);
LOGI("get vector_elementAt: %d, value: [%s]", i, value);
env->DeleteLocalRef(key);
}
}
}

}


return false;
}

}

2016年3月4日 星期五

Android 開發筆記 - 使用 Java Reflection 製作 Event Handler 流程控制

在 Android 上練習一些題目時,有碰到一連串 function call 的需求,特別是把每項工作做成 RISC 架構後,出現了重複使用的可能性,假設有 A, B, C, D 四個函式,有時任務是 A->B->D,有時是 B->C->A 的流程,這時就想找一個可以彈性設置任務流程的機制,有一點像 C/C++ 的 function pointer 或 virtual function 可以動態決定對象的機制,就隨意亂打關鍵字,想知道有沒可能得知 function name 後可以呼叫 function 做事,就找到 Java Reflection :http://docs.oracle.com/javase/tutorial/reflect/member/methodInvocation.html

於是乎,在 Java 環境中透過 ArrayList 記錄此次工作依序將執行的任務,每次挑一個出來用,就達成任務了 :P

紀錄一下(此例各個函式在此例沒有傳遞參數):

class Job {
private String tag = "Job";
private List<String> mFunctionNameList = new ArrayList<String>();

void callFunctions() {
if (mFunctionNameList.size() > 0) {
String methodName = mFunctionNameList.get(0);
mFunctionNameList.remove(0);
try {
Job.this.getClass().getDeclaredMethod(methodName).invoke(this);
} catch (Exception e) {
}
}
}

// call A -> C -> D
void task1() {
mFunctionNameList.clear();
mFunctionNameList.add("A");
mFunctionNameList.add("C");
mFunctionNameList.add("D");
callFunctions();
}

// call B -> C -> A
void task2() {
mFunctionNameList.clear();
mFunctionNameList.add("B");
mFunctionNameList.add("C");
mFunctionNameList.add("A");
callFunctions();
}

void A() {
Log.d(tag, "call A");
}
void B() {
Log.d(tag, "call B");
}
void C() {
Log.d(tag, "call C");
}
void D() {
Log.d(tag, "call D");
}
}

Android 開發筆記 - 從 Java 端傳遞 HashMap 物件 Android NDK/ JNI C/C++ 操作變更內容

C/C++ Code:

#include <jni.h>

extern "C" {

jobject Java_YourPackageName_updateHashMap(JNIEnv *env, jobject thiz, jobject hashmap) {
    if (!hashmap)
        return hashmap;

    jclass mapClass = env->GetObjectClass(hashmap);
    jmethodID obj_put = env->GetMethodID(mapClass, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");

    if (mapClass && obj_put) {
        jstring key = env->NewStringUTF("Haha");
        jstring value = env->NewStringUTF("Hehe");
        env->CallObjectMethod(hashmap, obj_put, key, value);
        env->DeleteLocalRef(key);
        env->DeleteLocalRef(value);
    }

    return hashmap;
}
}


Android Java code:

public native HashMap updateHashMap(HashMap<String, String>map);
static {
        System.loadLibrary("native");
}

void usage() {
HashMap<String, String> mHashMap = new HashMap<String, String>();

        Log.d(tag, "HashMap size: "+mHashMap.size());
updateHashMap(mHashMap);
        Log.d(tag, "HashMap size: "+mHashMap.size());

        Iterator it = mHashMap.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry pair = (Map.Entry)it.next();
            Log.d(tag, "HashMap key: ["+pair.getKey()+"], value: ["+pair.getValue().toString()+"]");
        }
}

Android 開發筆記 - 使用 Android NDK / JNI 回傳 HashMap 物件至 Java 端

C/C++ Code:

#include <jni.h>

extern "C" {

jobject Java_YourPackageName_getHashMap(JNIEnv *env, jobject thiz) {

    jclass mapClass = env->FindClass("java/util/HashMap");
    jmethodID obj_init = env->GetMethodID(mapClass, "<init>", "(I)V");
    jmethodID obj_put = env->GetMethodID(mapClass, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
    jobject hashMap = env->NewObject(mapClass, obj_init, 1);

    /*
    const char * input_data = "HelloWorld";
    int input_data_length = strlen(input_data);

    jbyteArray data = env->NewByteArray(input_data_length);
    env->SetByteArrayRegion(data, 0, input_data_length, (const jbyte *)input_data);
    env->CallObjectMethod(hashMap, obj_put, (jbyteArray)data, (jbyteArray)data);
    env->DeleteLocalRef(data);
    */
    jstring key = env->NewStringUTF("Hello");
    jstring value = env->NewStringUTF("World");
    env->CallObjectMethod(hashMap, obj_put, key, value);
    env->DeleteLocalRef(key);
    env->DeleteLocalRef(value);

    //env->DeleteLocalRef(hashMap);
    return hashMap;
}

}


Android Java code:

public native HashMap getHashMap();
static {
        System.loadLibrary("native");
}

void usage() {
HashMap<String, String> mHashMap = getHashMap();

        Log.d(tag, "HashMap size: "+mHashMap.size());
        Log.d(tag, "HashMap data with key(Hello): "+mHashMap.get("Hello"));
        Iterator it = mHashMap.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry pair = (Map.Entry)it.next();
            Log.d(tag, "HashMap key: ["+pair.getKey()+"], value: ["+pair.getValue().toString()+"]");
        }
}

設定 Nginx 透過 proxy_pass 存取 Apache web server + SVN + DAV 服務 @ Ubuntu 14.04

這個故事是這樣的,當所有的 server deploy 都用預設用 nginx 後,有個成員跳出來說要用 SVN over HTTP 服務時,就發現必須架設 Apache Web server ,因為 SVN over HTTP 的相關模組就只有 Apache Web server 才有,所以就變成此況的窘境了。

總之,架設 Apache web server 很容易,而安裝 mod_dav_svn 也差不多:

$ sudo apt-get install subversion libapache2-mod-svn apache2
$ sudo vim /etc/apache2/ports.conf
從 Listen 80 改成 Listen 8000,避開跟 Nginx 預設 80 port 衝到
$ sudo vim /etc/apache2/sites-available/000-default.conf
從 <VirtualHost *:80> 改成 <VirtualHost *:8000>
$ sudo a2enmod dav_svn authz_svn
$ sudo vim /etc/apache2/conf-available/svn.conf
<Location /svn>
        DAV svn
        SVNParentPath /path/svn
        AuthzSVNAccessFile /path/svn_access
        AuthType Basic
        AuthName "Web svn"
        AuthUserFile /path/svn_auth
        Require valid-user
</Location>
$ sudo service apache2 restart


接著設定 Nginx:

location /svn/ {
proxy_pass http://127.0.0.1:8000/svn/;
}

# 更新:假設 svn repo 裡有 .ht 開頭的檔案,可能會被 nginx 條件擋下
location ~ /svn/.*\.ht {
proxy_pass http://127.0.0.1:8000/;
}


如此一來即可搞定。