8

管理環境: IE8、IIS 7、ColdFusion

IE から .mp3、.mpeg などのメディア ファイルを指す GET 要求を発行すると、ブラウザーは関連付けられたアプリケーション (Window Media Player) を起動し、IIS がファイルを提供する方法によってアプリケーションがストリーミングできるようになると思います。それ。

特定の時間に特定のユーザーのみに許可できるように、ファイルのストリーミング プロセスを完全に制御できるようにしたいと考えています。そのため、単純に IIS にファイルを直接提供させることはできず、代わりに ColdFusion を使用してファイルを提供したいと考えました。

いくつかの異なるアプローチを試みましたが、いずれの場合も、ブラウザは外部アプリケーションを起動する前にファイル コンテンツ全体をダウンロードしています。それは私たちが避けたいことです。

NTFS パーミッション ベースのソリューションは必要ないことに注意してください。

最も有望と思われる解決策は、ColdFusion を使用して、ファイル全体をメモリにロードせずにファイルをクライアントにストリーミングすることでしたが、唯一の利点は、ブラウザに提供されたときにファイルが完全にメモリにロードされないことでした。ブラウザは転送が終了するまで待機し、Winwodw Media Player でファイルを開きます。

ColdFusion または Java を使用してファイルをブラウザにストリーミングし、IIS にファイルを直接提供させる場合と同様に、ブラウザが関連するアプリケーションに処理を委任する方法はありますか?

4

3 に答える 3

1

作業ソリューション:

最終的に、シークをサポートし、あまり手間をかけないソリューションを見つけることができました。基本的にHttpForwardRequest、HTTP ヘッダーなどの他の初期サーブレット要求の詳細を保持しながら、指定されたメディア URL に新しい HTTP 要求を発行することによって、要求処理を Web サーバーに委任するコンポーネントを作成しました。Web サーバーの応答は、サーブレットの応答出力ストリームにパイプされます。

私たちの場合、Web サーバー (ISS 7.0) は既に HTTP ストリーミングの方法を知っているので、これだけを行う必要があります。

注:試してみましgetRequestDispatcher('some_media_url').forward(...)たが、正しいヘッダーを持つメディア ファイルを提供できないようです。

HttpForwardRequestコード:

<cfcomponent output="no">

    <cffunction name="init" access="public" returntype="HttpForwardRequest" output="no">
        <cfargument name="url" type="string" required="yes" hint="The URL to which the request should be forwarded to.">
        <cfargument name="requestHeaders" type="struct" required="yes" hint="The HTTP request headers.">
        <cfargument name="response" type="any" required="yes" hint=" The servlet's response object.">
        <cfargument name="responseHeaders" type="struct" required="no" default="#{}#" hint="Custom response headers to override the initial request response headers.">

        <cfset variables.instance = {
            url = arguments.url,
            requestHeaders = arguments.requestHeaders,
            response = arguments.response,
            responseHeaders = arguments.responseHeaders
        }>

        <cfreturn this>
    </cffunction>

    <cffunction name="send" access="public" returntype="void" output="no">
        <cfset var response = variables.instance.response>
        <cfset var outputStream = response.getOutputStream()>
        <cfset var buffer = createBuffer()>

        <cftry>

            <cfset var connection = createObject('java', 'java.net.URL')
                    .init(variables.instance.url)
                    .openConnection()>

            <cfset setRequestHeaders(connection)>

            <cfset setResponseHeaders(connection)>

            <cfset var inputStream = connection.getInputStream()>

            <cfset response.setStatus(connection.getResponseCode(), connection.getResponseMessage())>

            <cfloop condition="true">
                <cfset var bytesRead = inputStream.read(buffer, javaCast('int', 0), javaCast('int', arrayLen(buffer)))>

                <cfif bytesRead eq -1>
                    <cfbreak>
                </cfif>

                <cftry>
                    <cfset outputStream.write(buffer, javaCast('int', 0), bytesRead)>

                    <cfset outputStream.flush()>

                    <!--- 
                    Connection reset by peer: socket write error

                    The above error occurs when users are seeking a video.
                    That is probably normal since I assume the client (e.g. Window Media Player) 
                    closes the connection when seeking.
                    --->
                    <cfcatch type="java.net.SocketException">
                        <cfbreak>
                    </cfcatch>
                </cftry>
            </cfloop>

            <cffinally>

                <cfif not isNull(inputStream)>
                    <cfset inputStream.close()>
                </cfif>

                <cfif not isNull(connection)>
                    <cfset connection.disconnect()>
                </cfif>

            </cffinally>
        </cftry>

    </cffunction>

    <cffunction name="setRequestHeaders" access="private" returntype="void" output="no">

        <cfargument name="connection" type="any" required="yes">

        <cfset var requestHeaders = variables.instance.requestHeaders>

        <cfloop collection="#requestHeaders#" item="local.key">
            <cfset arguments.connection.setRequestProperty(key, requestHeaders[key])>
        </cfloop>

    </cffunction>

    <cffunction name="setResponseHeaders" access="private" returntype="void" output="no">
        <cfargument name="connection" type="any" required="yes">

        <cfset var response = variables.instance.response>
        <cfset var responseHeaders = variables.instance.responseHeaders>
        <cfset var i = -1>

        <!--- Copy connection headers --->
        <cfloop condition="true">

            <cfset i = javaCast('int', i + 1)>

            <cfset var key = arguments.connection.getHeaderFieldKey(i)>

            <cfset var value = arguments.connection.getHeaderField(i)>

            <cfif isNull(key)>
                <cfif isNull(value)>
                    <!--- Both, key and value are null, break --->
                    <cfbreak>
                </cfif>

                <!--- Sometimes the key is null but the value is not, just ignore and keep iterating --->
                <cfcontinue>
            </cfif>

            <cfset setResponseHeader(key, value)>
        </cfloop>

        <!--- Apply custom headers --->
        <cfloop collection="#responseHeaders#" item="key">
            <cfset setResponseHeader(key, responseHeaders[key])>
        </cfloop>

    </cffunction>

    <cffunction name="setResponseHeader" access="private" returntype="void" output="no">
        <cfargument name="key" type="string" required="yes">
        <cfargument name="value" type="string" required="yes">

        <cfset var response = variables.instance.response>

        <cfif arguments.key eq 'Content-Type'>
            <cfset response.setContentType(arguments.value)>
        <cfelse>
            <cfset response.setHeader(arguments.key, arguments.value)>
        </cfif>
    </cffunction>

    <cffunction name="createBuffer" access="private" returntype="any" output="no">
        <cfreturn repeatString("12345", 1024).getBytes()>
    </cffunction>

</cfcomponent>

cf_streamurlコード:

<cfparam name="attributes.url" type="url">

<cfif thisTag.executionMode neq 'start'>
    <cfexit>
</cfif>

<cfset pageContext = getPageContext()>

<cfset requestHeaders = {
    'Authorization' = 'Anonymous'
}>

<cfset structAppend(requestHeaders, getHTTPRequestData().headers, false)>

<cfset pageContext.setFlushOutput(false)>

<!--- Forward the request to IIS --->
<cfset new references.cfc.servlet.HttpForwardRequest(
    attributes.url,
    requestHeaders,
    pageContext.getResponse().getResponse()
).send()>

cf_streamurl次に、次のようなカスタム タグを使用できます。

<cf_streamurl url="http://sh34lprald94/media_stream/unprotected/trusts.mp4"/>

重要: 現在のところ、匿名認証のみがサポートされています。


最初の半作業の試み (歴史的な目的のみ):

応答パケットの HTTP ヘッダーを検査し、IIS にメディア ファイルをサーバーさせるときに返される MIME タイプを調べることで、ニーズに合ったソリューション (実際には非常に単純なソリューション) を見つけました。

問題は、ColdFusion を使用してファイル コンテンツをブラウザに提供しようとするときに、 Window Media ServicesのMIME タイプの 1 つを使用して、ブラウザが処理を Window Media Player に直接委任するよう強制する必要があったことです (これにより、コンテンツをストリーミングできるようになります)。ファイル)。

File extension MIME type 
.asf video/x-ms-asf 
.asx video/x-ms-asf 
.wma audio/x-ms-wma 
.wax audio/x-ms-wax 
.wmv audio/x-ms-wmv 
.wvx video/x-ms-wvx 
.wm video/x-ms-wm 
.wmx video/x-ms-wmx 
.wmz application/x-ms-wmz 
.wmd application/x-ms-wmd

この問題を解決するための最初のステップは、ファイルの拡張子に基づいて MIME タイプを正しく解決する関数を作成することでした。IIS には既にその知識がありますが、MIME レジストリを照会する方法はまだ見つかりません。

注:wmsMimeTypesは、WMS MIME タイプを検索するためのマップとして使用される構造体です。

<cffunction name="getMimeType" access="public" returntype="string">
    <cfargument name="fileName" type="string" required="yes">

    <cfset var mimeType = 'application/x-unknown'>
    <cfset var ext = this.getFileExtension(arguments.fileName)>

    <cfif structKeyExists(this.wmsMimeTypes, ext)>
        <cfreturn this.wmsMimeTypes[ext]>
    </cfif>

    <!--- TODO: Is there a way to read the IIS MIME registry? --->
    <cfregistry action="get" branch="HKEY_CLASSES_ROOT\.#ext#" entry="Content Type" variable="mimeType">

    <cfreturn mimeType>

</cffunction>

次に、ColdFusion を使用して、ファイル全体をメモリにロードせずにファイルをクライアントstreamにストリーミングするにある実装に基づいて、ストリーミング プロセスをカプセル化する以下のようなメソッドを実装しました。

注: も動作しcfcontentますが、特にブラウザにフラッシュする前にファイル全体をメモリにロードするため、リソースを大量に消費するため、非常に非効率的であると読みました。

<cffunction name="stream" access="public" returntype="void">
    <cfargument name="file" type="string" required="yes">
    <cfargument name="mimeType" type="string" required="no">

    <cfscript>
        var fileName = getFileFromPath(arguments.file);
        var resolvedMimeType = structKeyExists(arguments, 'mimeType')? arguments.mimeType : this.getMimeType(fileName);
        var javaInt0 = javaCast('int', 0);
        var response = getPageContext().getResponse().getResponse();
        var binaryOutputStream = response.getOutputStream();
        var bytesBuffer = repeatString('11111', 1024).getBytes();
        var fileInputStream = createObject('java', 'java.io.FileInputStream').init(javaCast('string', getRootPath() & arguments.file));

        getPageContext().setFlushOutput(javaCast('boolean', false));

        response.resetBuffer();
        response.setContentType(javaCast('string', resolvedMimeType));

        try {
            while (true) {
                bytesRead = fileInputStream.read(bytesBuffer, javaInt0, javaCast('int', arrayLen(bytesBuffer)));

                if (bytesRead eq -1) break;

                binaryOutputStream.write(bytesBuffer, javaInt0, javaCast('int', bytesRead));
                binaryOutputStream.flush();
            }               
            response.reset();
         } finally {
             if (not isNull(fileInputStream)) fileInputStream.close();
             if (not isNull(binaryOutputStream)) binaryOutputStream.close();
         }
    </cfscript>
</cffunction>

ヘッダーを設定しないでください。そうしないContent-Dispositionと、コントロールを WMP に委譲する代わりに、ブラウザーがファイルをダウンロードします。

注: @Miguel-F が提案した記事に記載されているように、Web サーバーにファイルをクライアント (または使用した CF ソリューション) にストリーミングさせることは、メディア サーバーを使用するほど効率的ではありません

主な欠点: 以前の実装ではシークがサポートされていないため、実際にはソリューションがほとんど使用できなくなる可能性があります。

于 2013-10-15T18:28:28.730 に答える